-
# frozen_string_literal: true
-
-
1
module Admin
-
1
class AdminController < ApplicationController
-
1
before_action :authenticate_user!
-
1
before_action :redirect_unless_admin
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
module Admin
-
1
class PodsController < AdminController
-
1
respond_to :html, :json, :mobile
-
-
1
def index
-
3
pods_json = PodPresenter.as_collection(Pod.all)
-
-
3
respond_with do |format|
-
3
format.html do
-
2
gon.preloads[:pods] = pods_json
-
2
gon.unchecked_count = Pod.unchecked.count
-
2
gon.version_failed_count = Pod.version_failed.count
-
2
gon.error_count = Pod.check_failed.count
-
2
gon.active_count = Pod.active.count
-
2
gon.total_count = Pod.count
-
2
render "admins/pods"
-
end
-
3
format.mobile { render "admins/pods" }
-
4
format.json { render json: pods_json }
-
end
-
end
-
-
1
def recheck
-
2
pod = Pod.find(params[:pod_id])
-
2
pod.test_connection!
-
-
2
respond_with do |format|
-
3
format.html { redirect_to admin_pods_path }
-
3
format.json { render json: PodPresenter.new(pod).as_json }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Admin
-
1
class UsersController < AdminController
-
1
before_action :validate_user, only: %i(make_admin remove_admin make_moderator remove_moderator make_spotlight remove_spotlight)
-
-
1
def close_account
-
1
u = User.find(params[:id])
-
1
u.close_account!
-
1
redirect_to user_search_path, notice: t("admins.user_search.account_closing_scheduled", name: u.username)
-
end
-
-
1
def lock_account
-
u = User.find(params[:id])
-
u.lock_access!
-
redirect_to user_search_path, notice: t("admins.user_search.account_locking_scheduled", name: u.username)
-
end
-
-
1
def unlock_account
-
u = User.find(params[:id])
-
u.unlock_access!
-
redirect_to user_search_path, notice: t("admins.user_search.account_unlocking_scheduled", name: u.username)
-
end
-
-
1
def make_admin
-
unless Role.is_admin? @user.person
-
Role.add_admin @user.person
-
notice = "admins.user_search.add_admin"
-
else
-
notice = "admins.user_search.role_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
def remove_admin
-
if Role.is_admin? @user.person
-
Role.remove_admin @user.person
-
notice = "admins.user_search.delete_admin"
-
else
-
notice = "admins.user_search.role_removal_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
def make_moderator
-
unless Role.moderator_only? @user.person
-
Role.add_moderator @user.person
-
notice = "admins.user_search.add_moderator"
-
else
-
notice = "admins.user_search.role_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
def remove_moderator
-
if Role.moderator_only? @user.person
-
Role.remove_moderator @user.person
-
notice = "admins.user_search.delete_moderator"
-
else
-
notice = "admins.user_search.role_removal_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
def make_spotlight
-
unless Role.spotlight? @user.person
-
Role.add_spotlight @user.person
-
notice = "admins.user_search.add_spotlight"
-
else
-
notice = "admins.user_search.role_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
def remove_spotlight
-
if Role.spotlight? @user.person
-
Role.remove_spotlight @user.person
-
notice = "admins.user_search.delete_spotlight"
-
else
-
notice = "admins.user_search.role_removal_implemented"
-
end
-
redirect_to user_search_path, notice: t(notice, name: @user.username)
-
end
-
-
1
private
-
-
1
def validate_user
-
@user = User.where(id: params[:id]).first
-
redirect_to user_search_path, notice: t("admins.user_search.does_not_exist") unless @user
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class AdminsController < Admin::AdminController
-
1
include ApplicationHelper
-
-
1
def dashboard
-
4
gon.push(pod_version: pod_version)
-
end
-
-
1
def user_search
-
5
if params[:admins_controller_user_search]
-
3
search_params = params.require(:admins_controller_user_search)
-
.permit(:username, :email, :guid, :under13)
-
3
@search = UserSearch.new(search_params)
-
3
@users = @search.perform
-
end
-
-
5
@search ||= UserSearch.new
-
5
@users ||= []
-
end
-
-
1
def admin_inviter
-
4
inviter = InvitationCode.default_inviter_or(current_user)
-
4
email = params[:identifier]
-
4
user = User.find_by_email(email)
-
-
4
unless user
-
3
EmailInviter.new(email, inviter).send!
-
3
flash[:notice] = "invitation sent to #{email}"
-
else
-
1
flash[:notice]= "error sending invite to #{email}"
-
end
-
4
redirect_to user_search_path, :notice => flash[:notice]
-
end
-
-
1
def add_invites
-
InvitationCode.find_by_token(params[:invite_code_id]).add_invites!
-
redirect_to user_search_path
-
end
-
-
1
def weekly_user_stats
-
@created_users_by_week = Hash.new{ |h,k| h[k] = [] }
-
@created_users = User.where("username IS NOT NULL and created_at IS NOT NULL")
-
@created_users.find_each do |u|
-
week = u.created_at.beginning_of_week.strftime("%Y-%m-%d")
-
@created_users_by_week[week] << {username: u.username, closed_account: u.person.closed_account}
-
end
-
-
@selected_week = params[:week] || @created_users_by_week.keys.last
-
@counter = @created_users_by_week[@selected_week].count
-
end
-
-
1
def stats
-
4
@popular_tags = ActsAsTaggableOn::Tagging.joins(:tag)
-
.limit(50)
-
.order(Arel.sql("count(taggings.id) DESC"))
-
.group(:tag)
-
.count
-
-
4
case params[:range]
-
when "week"
-
1
range = 1.week
-
1
@segment = t('admins.stats.week')
-
when "2weeks"
-
1
range = 2.weeks
-
1
@segment = t('admins.stats.2weeks')
-
when "month"
-
1
range = 1.month
-
1
@segment = t('admins.stats.month')
-
else
-
1
range = 1.day
-
1
@segment = t('admins.stats.daily')
-
end
-
-
4
[Post, Comment, AspectMembership, User].each do |model|
-
16
create_hash(model, :range => range)
-
end
-
-
4
@posts_per_day = Post.where("created_at >= ?", Time.zone.today - 21.days)
-
.group(Arel.sql("DATE(created_at)"))
-
.order(Arel.sql("DATE(created_at) ASC"))
-
.count
-
4
@most_posts_within = @posts_per_day.values.max.to_f
-
-
4
@user_count = User.count
-
-
#@posts[:new_public] = Post.where(:type => ['StatusMessage','ActivityStreams::Photo'],
-
# :public => true).order('created_at DESC').limit(15).all
-
-
end
-
-
1
private
-
-
1
def percent_change(today, yesterday)
-
16
sprintf( "%0.02f", ((today-yesterday) / yesterday.to_f)*100).to_f
-
end
-
-
1
def create_hash(model, opts={})
-
16
opts[:range] ||= 1.day
-
16
plural = model.to_s.underscore.pluralize
-
16
eval(<<DATA
-
@#{plural} = {
-
:day_before => #{model}.where(:created_at => ((Time.now.midnight - #{opts[:range]*2})..Time.now.midnight - #{opts[:range]})).count,
-
:yesterday => #{model}.where(:created_at => ((Time.now.midnight - #{opts[:range]})..Time.now.midnight)).count
-
}
-
@#{plural}[:change] = percent_change(@#{plural}[:yesterday], @#{plural}[:day_before])
-
DATA
-
)
-
end
-
-
-
1
class UserSearch
-
1
include ActiveModel::Model
-
1
include ActiveModel::Conversion
-
1
include ActiveModel::Validations
-
-
1
attr_accessor :username, :email, :guid, :under13
-
-
1
validate :any_searchfield_present?
-
-
1
def initialize(attributes={})
-
5
assign_attributes(attributes)
-
5
yield(self) if block_given?
-
end
-
-
1
def assign_attributes(values)
-
5
values.each do |k, v|
-
3
public_send("#{k}=", v)
-
end
-
end
-
-
1
def any_searchfield_present?
-
10
if %w(username email guid under13).all? { |attr| public_send(attr).blank? }
-
errors.add :base, "no fields for search set"
-
end
-
end
-
-
1
def perform
-
3
return User.none unless valid?
-
-
3
users = User.arel_table
-
3
people = Person.arel_table
-
3
profiles = Profile.arel_table
-
3
res = User.joins(person: :profile)
-
3
res = res.where(users[:username].matches("%#{username}%")) unless username.blank?
-
3
res = res.where(users[:email].matches("%#{email}%")) unless email.blank?
-
3
res = res.where(people[:guid].matches("%#{guid}%")) unless guid.blank?
-
3
res = res.where(profiles[:birthday].gt(Date.today-13.years)) if under13 == '1'
-
3
res
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class AuthorizationsController < ApplicationController
-
1
rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e|
-
3
logger.info e.backtrace[0, 10].join("\n")
-
3
error, _description = e.message.split(" :: ")
-
3
handle_params_error(error, "The request was malformed: please double check the client id and redirect uri.")
-
end
-
-
1
rescue_from OpenSSL::SSL::SSLError do |e|
-
logger.info e.backtrace[0, 10].join("\n")
-
handle_params_error("bad_request", e.message)
-
end
-
-
1
rescue_from JSON::JWS::VerificationFailed do |e|
-
logger.info e.backtrace[0, 10].join("\n")
-
handle_params_error("bad_request", e.message)
-
end
-
-
1
before_action :auth_user_unless_prompt_none!
-
-
1
def new
-
33
auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(params[:client_id],
-
current_user, params[:scope])
-
33
reset_auth(auth)
-
33
if logged_in_before?(params[:max_age])
-
reauthenticate(params)
-
33
elsif params[:prompt]
-
2
prompt = params[:prompt].split(" ")
-
2
handle_prompt(prompt, auth)
-
else
-
31
handle_authorization_form(auth)
-
end
-
end
-
-
1
def create
-
12
restore_request_parameters
-
12
process_authorization_consent(params[:approve])
-
end
-
-
1
def destroy
-
2
authorization = Api::OpenidConnect::Authorization.find_by(id: params[:id])
-
2
if authorization
-
1
authorization.destroy
-
else
-
1
flash[:error] = I18n.t("api.openid_connect.authorizations.destroy.fail", id: params[:id])
-
end
-
2
redirect_to api_openid_connect_user_applications_url
-
end
-
-
1
private
-
-
1
def reset_auth(auth)
-
33
return unless auth
-
3
auth.o_auth_access_tokens.destroy_all
-
3
auth.code_used = false
-
3
auth.save
-
end
-
-
1
def handle_prompt(prompt, auth)
-
2
if prompt.include? "select_account"
-
1
handle_params_error("account_selection_required",
-
"There is no support for choosing among multiple accounts")
-
1
elsif prompt.include? "consent"
-
1
request_authorization_consent_form
-
else
-
handle_authorization_form(auth)
-
end
-
end
-
-
1
def handle_authorization_form(auth)
-
31
if auth
-
2
process_authorization_consent("true")
-
else
-
29
request_authorization_consent_form
-
end
-
end
-
-
1
def request_authorization_consent_form
-
30
add_claims_to_scopes
-
30
endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user)
-
30
handle_start_point_response(endpoint)
-
end
-
-
1
def add_claims_to_scopes
-
30
return unless params[:claims]
-
1
claims_json = JSON.parse(params[:claims])
-
1
return unless claims_json
-
1
claims_array = claims_json["userinfo"].try(:keys)
-
1
return unless claims_array
-
1
req = build_rack_request
-
1
claims = claims_array.unshift(req[:scope]).join(" ")
-
1
req.update_param("scope", claims)
-
end
-
-
1
def logged_in_before?(seconds)
-
33
if seconds.nil?
-
33
false
-
else
-
(Time.now - current_user.current_sign_in_at) > seconds.to_i
-
end
-
end
-
-
1
def handle_start_point_response(endpoint)
-
30
status, header, _response = endpoint.call(request.env)
-
-
27
if status.in?([301, 302, 303, 307, 308])
-
4
redirect_to header["Location"]
-
else
-
23
save_params_and_render_consent_form(endpoint)
-
end
-
end
-
-
1
def save_params_and_render_consent_form(endpoint)
-
23
@o_auth_application = endpoint.o_auth_application
-
23
@response_type = endpoint.response_type
-
23
@redirect_uri = endpoint.redirect_uri
-
23
@scopes = endpoint.scopes
-
23
save_request_parameters
-
23
@app = UserApplicationPresenter.new @o_auth_application, @scopes
-
23
override_content_security_policy_directives(form_action: %w[])
-
23
render :new
-
end
-
-
1
def save_request_parameters
-
23
session[:client_id] = @o_auth_application.client_id
-
23
session[:response_type] = @response_type
-
23
session[:redirect_uri] = @redirect_uri
-
23
session[:scopes] = scopes_as_space_seperated_values
-
23
session[:state] = params[:state]
-
23
session[:nonce] = params[:nonce]
-
end
-
-
1
def scopes_as_space_seperated_values
-
23
@scopes.join(" ")
-
end
-
-
1
def process_authorization_consent(approved_string)
-
15
endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new(
-
current_user, to_boolean(approved_string))
-
15
handle_confirmation_endpoint_response(endpoint)
-
end
-
-
1
def handle_confirmation_endpoint_response(endpoint)
-
15
_status, header, _response = endpoint.call(request.env)
-
15
delete_authorization_session_variables
-
15
redirect_to header["Location"]
-
end
-
-
1
def delete_authorization_session_variables
-
15
session.delete(:client_id)
-
15
session.delete(:response_type)
-
15
session.delete(:redirect_uri)
-
15
session.delete(:scopes)
-
15
session.delete(:state)
-
15
session.delete(:nonce)
-
end
-
-
1
def to_boolean(str)
-
15
str.downcase == "true"
-
end
-
-
1
def restore_request_parameters
-
12
req = build_rack_request
-
12
req.update_param("client_id", session[:client_id])
-
12
req.update_param("redirect_uri", session[:redirect_uri])
-
12
req.update_param("response_type", response_type_as_space_seperated_values)
-
12
req.update_param("scope", session[:scopes])
-
12
req.update_param("state", session[:state])
-
12
req.update_param("nonce", session[:nonce])
-
end
-
-
1
def build_rack_request
-
13
Rack::Request.new(request.env)
-
end
-
-
1
def response_type_as_space_seperated_values
-
12
[*session[:response_type]].join(" ")
-
end
-
-
1
def handle_params_error(error, error_description)
-
9
if params[:client_id] && params[:redirect_uri]
-
7
handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description)
-
else
-
2
render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), error_description
-
end
-
end
-
-
1
def handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description)
-
7
app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id])
-
7
if app && app.redirect_uris.include?(params[:redirect_uri])
-
4
redirect_prompt_error_display(error, error_description)
-
else
-
3
render_error I18n.t("api.openid_connect.error_page.could_not_authorize"),
-
"Invalid client id or redirect uri"
-
end
-
end
-
-
1
def redirect_prompt_error_display(error, error_description)
-
4
redirect_params_hash = {error: error, error_description: error_description, state: params[:state]}
-
16
redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&")
-
4
redirect_to params[:redirect_uri] + "?" + redirect_fragment
-
end
-
-
1
def auth_user_unless_prompt_none!
-
53
prompt = params[:prompt]
-
53
if prompt && prompt.include?("none")
-
6
handle_prompt_none
-
47
elsif prompt && prompt.include?("login")
-
new_params = params.except("controller", "action").permit!.to_h.merge(prompt: prompt.remove("login"))
-
reauthenticate(new_params)
-
else
-
47
authenticate_user!
-
end
-
end
-
-
1
def handle_prompt_none
-
6
if params[:prompt] == "none"
-
5
if user_signed_in?
-
4
handle_prompt_with_signed_in_user
-
else
-
1
handle_params_error("login_required", "User must already be logged in when `prompt` is `none`")
-
end
-
else
-
1
handle_params_error("invalid_request", "The 'none' value cannot be used with any other prompt value")
-
end
-
end
-
-
1
def handle_prompt_with_signed_in_user
-
4
client_id = params[:client_id]
-
4
if client_id
-
4
auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(client_id,
-
current_user, params[:scope])
-
4
if auth
-
1
process_authorization_consent("true")
-
else
-
3
handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`")
-
end
-
else
-
handle_params_error("bad_request", "Client ID is missing from request")
-
end
-
end
-
-
1
def reauthenticate(params)
-
sign_out current_user
-
redirect_to new_api_openid_connect_authorization_path(params)
-
end
-
-
1
def render_error(error_description, detailed_error=nil)
-
5
@error_description = error_description
-
5
@detailed_error = detailed_error
-
5
if request.format == :mobile
-
render "api/openid_connect/error/error.mobile", layout: "application.mobile"
-
else
-
5
render "api/openid_connect/error/error", layout: "with_header_with_footer"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class ClientsController < ApplicationController
-
1
skip_before_action :verify_authenticity_token
-
-
1
rescue_from OpenIDConnect::HttpError do |e|
-
http_error_page_as_json(e)
-
end
-
-
1
rescue_from OpenIDConnect::ValidationFailed,
-
ActiveRecord::RecordInvalid, Api::OpenidConnect::Error::InvalidSectorIdentifierUri do |e|
-
1
validation_fail_as_json(e)
-
end
-
-
1
rescue_from Api::OpenidConnect::Error::InvalidRedirectUri do |e|
-
validation_fail_redirect_uri(e)
-
end
-
-
1
rescue_from OpenSSL::SSL::SSLError do |e|
-
validation_fail_as_json(e)
-
end
-
-
# Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/controllers/connect/clients_controller.rb#L24
-
1
def create
-
7
registrar = OpenIDConnect::Client::Registrar.new(request.url, params)
-
7
client = Api::OpenidConnect::OAuthApplication.register! registrar
-
6
render json: client.as_json(root: false)
-
end
-
-
1
def find
-
2
client = Api::OpenidConnect::OAuthApplication.find_by(client_name: params[:client_name])
-
2
if client
-
1
render json: {client_id: client.client_id}
-
else
-
1
render json: {error: "Client with name #{params[:client_name]} does not exist"}
-
end
-
end
-
-
1
private
-
-
1
def http_error_page_as_json(e)
-
render json: {error: :invalid_request, error_description: e.message}, status: 400
-
end
-
-
1
def validation_fail_as_json(e)
-
1
render json: {error: :invalid_client_metadata, error_description: e.message}, status: 400
-
end
-
-
1
def validation_fail_redirect_uri(e)
-
render json: {error: :invalid_redirect_uri, error_description: e.message}, status: 400
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/app/controllers/discovery_controller.rb
-
-
1
module Api
-
1
module OpenidConnect
-
1
class DiscoveryController < ApplicationController
-
1
def configuration
-
3
render json: OpenIDConnect::Discovery::Provider::Config::Response.new(
-
issuer: AppConfig.environment.url,
-
registration_endpoint: api_openid_connect_clients_url,
-
authorization_endpoint: new_api_openid_connect_authorization_url,
-
token_endpoint: api_openid_connect_access_tokens_url,
-
userinfo_endpoint: api_openid_connect_user_info_url,
-
jwks_uri: api_openid_connect_url,
-
scopes_supported: Api::OpenidConnect::Authorization::SCOPES,
-
response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,
-
request_object_signing_alg_values_supported: %i(none),
-
request_parameter_supported: true,
-
request_uri_parameter_supported: true,
-
subject_types_supported: %w(public pairwise),
-
id_token_signing_alg_values_supported: %i(RS256),
-
token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt),
-
claims_parameter_supported: true,
-
claims_supported: %w(sub name nickname profile picture),
-
userinfo_signing_alg_values_supported: %w(none)
-
)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
module Api
-
1
module OpenidConnect
-
1
class IdTokensController < ApplicationController
-
1
def jwks
-
1
render json: JSON::JWK::Set.new(build_jwk).as_json
-
end
-
-
1
private
-
-
1
def build_jwk
-
1
JSON::JWK.new(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY, use: :sig, kid: :default)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class TokenEndpointController < ApplicationController
-
1
skip_before_action :verify_authenticity_token
-
-
1
def create
-
27
req = Rack::Request.new(request.env)
-
27
if req["client_assertion_type"] == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
-
7
handle_jwt_bearer(req)
-
end
-
23
self.status, headers, self.response_body = Api::OpenidConnect::TokenEndpoint.new.call(request.env)
-
95
headers.each {|name, value| response.headers[name] = value }
-
nil
-
end
-
-
1
private
-
-
1
def handle_jwt_bearer(req)
-
7
jwt_string = req["client_assertion"]
-
7
jwt = JSON::JWT.decode jwt_string, :skip_verification
-
4
o_auth_app = Api::OpenidConnect::OAuthApplication.find_by(client_id: jwt["iss"])
-
4
raise Rack::OAuth2::Server::Authorize::BadRequest(:invalid_request) unless o_auth_app
-
4
public_key = fetch_public_key(o_auth_app, jwt)
-
4
JSON::JWT.decode(jwt_string, JSON::JWK.new(public_key).to_key)
-
3
req.update_param("client_id", o_auth_app.client_id)
-
3
req.update_param("client_secret", o_auth_app.client_secret)
-
end
-
-
1
def fetch_public_key(o_auth_app, jwt)
-
4
public_key = fetch_public_key_from_json(o_auth_app.jwks, jwt)
-
4
if public_key.empty? && o_auth_app.jwks_uri
-
response = Faraday.get(o_auth_app.jwks_uri)
-
public_key = fetch_public_key_from_json(response.body, jwt)
-
end
-
4
raise Rack::OAuth2::Server::Authorize::BadRequest(:unauthorized_client) if public_key.empty?
-
4
public_key
-
end
-
-
1
def fetch_public_key_from_json(string, jwt)
-
4
json = JSON.parse(string)
-
4
keys = json["keys"]
-
4
public_key = get_key_from_kid(keys, jwt.header["kid"])
-
4
public_key
-
end
-
-
1
def get_key_from_kid(keys, kid)
-
4
keys.each do |key|
-
8
return key if key.has_value?(kid)
-
end
-
end
-
-
1
rescue_from Rack::OAuth2::Server::Authorize::BadRequest,
-
JSON::JWT::InvalidFormat, JSON::JWK::UnknownAlgorithm do |e|
-
3
logger.info e.backtrace[0, 10].join("\n")
-
3
render json: {error: :invalid_request, error_description: e.message, status: 400}
-
end
-
1
rescue_from JSON::JWT::VerificationFailed do |e|
-
1
logger.info e.backtrace[0, 10].join("\n")
-
1
render json: {error: :invalid_grant, error_description: e.message, status: 400}
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class UserApplicationsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def index
-
1
@user_apps = UserApplicationsPresenter.new current_user
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class UserInfoController < ApplicationController
-
1
include Api::OpenidConnect::ProtectedResourceEndpoint
-
-
1
before_action do
-
5
require_access_token %w(openid)
-
end
-
-
1
def show
-
2
serializer = UserInfoSerializer.new(current_user)
-
2
auth = current_token.authorization
-
2
serializer.serialization_options = {authorization: auth}
-
attributes_without_essential =
-
12
serializer.attributes.with_indifferent_access.select {|scope| auth.scopes.include? scope }
-
2
attributes = attributes_without_essential.merge(
-
sub: serializer.sub)
-
2
render json: attributes.to_json
-
end
-
-
1
def current_user
-
12
current_token ? current_token.authorization.user : nil
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class AspectsController < Api::V1::BaseController
-
1
before_action except: %i[create update destroy] do
-
5
require_access_token %w[contacts:read]
-
end
-
-
1
before_action only: %i[create update destroy] do
-
14
require_access_token %w[contacts:modify]
-
end
-
-
1
def index
-
1
aspects_query = current_user.aspects
-
1
aspects_page = index_pager(aspects_query).response
-
3
aspects_page[:data] = aspects_page[:data].map {|a| aspect_as_json(a, false) }
-
1
render_paged_api_response aspects_page
-
end
-
-
1
def show
-
2
aspect = current_user.aspects.where(id: params[:id]).first
-
2
if aspect
-
1
render json: aspect_as_json(aspect, true)
-
else
-
1
render_error 404, "Aspect with provided ID could not be found"
-
end
-
end
-
-
1
def create
-
3
params.require(%i[name])
-
2
aspect = current_user.aspects.build(name: params[:name])
-
2
if aspect&.save
-
1
render json: aspect_as_json(aspect, true)
-
else
-
1
render_error 422, "Failed to create the aspect"
-
end
-
rescue ActionController::ParameterMissing
-
1
render_error 422, "Failed to create the aspect"
-
end
-
-
1
def update
-
6
aspect = current_user.aspects.where(id: params[:id]).first
-
-
6
if !aspect
-
1
render_error 404, "Failed to update the aspect"
-
5
elsif aspect.update!(aspect_params(true))
-
4
render json: aspect_as_json(aspect, true)
-
else
-
render_error 422, "Failed to update the aspect"
-
end
-
rescue ActionController::ParameterMissing, ActiveRecord::RecordInvalid
-
1
render_error 422, "Failed to update the aspect"
-
end
-
-
1
def destroy
-
2
aspect = current_user.aspects.where(id: params[:id]).first
-
2
if aspect&.destroy
-
1
head :no_content
-
else
-
1
render_error 422, "Failed to delete the aspect"
-
end
-
end
-
-
1
private
-
-
1
def aspect_params(allow_order=false)
-
5
parameters = params.permit(:name)
-
5
parameters[:order_id] = params[:order] if params.has_key?(:order) && allow_order
-
-
5
parameters
-
end
-
-
1
def aspect_as_json(aspect, as_full)
-
8
AspectPresenter.new(aspect).as_api_json(as_full)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class BaseController < ApplicationController
-
1
include Api::OpenidConnect::ProtectedResourceEndpoint
-
-
404
protect_from_forgery unless: -> { request.format.json? }
-
-
1
protected
-
-
1
rescue_from Exception do |e|
-
logger.error e.message
-
logger.error e.backtrace.join("\n")
-
render_error 500, e.message
-
end
-
-
1
rescue_from Rack::OAuth2::Server::Resource::Bearer::Unauthorized do |e|
-
logger.error e.message
-
render_error 403, e.message
-
end
-
-
1
rescue_from Rack::OAuth2::Server::Resource::Forbidden do |e|
-
47
logger.error e.message
-
47
render_error 403, e.message
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do |e|
-
logger.error e.message
-
render_error 404, "No record found for the given id"
-
end
-
-
1
rescue_from ActiveRecord::RecordInvalid do |e|
-
logger.error e.message
-
render_error 422, e.message
-
end
-
-
1
rescue_from ActionController::ParameterMissing do |e|
-
2
logger.error e.message
-
2
render_error 422, e.message.split("\n").first
-
end
-
-
1
def current_user
-
1500
current_token ? current_token.authorization.user : nil
-
end
-
-
1
def index_pager(query)
-
28
Api::Paging::RestPaginatorBuilder.new(query, request).index_pager(params)
-
end
-
-
1
def render_paged_api_response(page)
-
75
link_header = []
-
75
link_header << %(<#{page[:links][:next]}>; rel="next") if page[:links][:next]
-
75
link_header << %(<#{page[:links][:previous]}>; rel="previous") if page[:links][:previous]
-
75
response.set_header("Link", link_header.join(", ")) if link_header.present?
-
-
75
render json: page[:data]
-
end
-
-
1
def render_error(code, message)
-
191
render json: {code: code, message: message}, status: code
-
end
-
-
1
def time_pager(query)
-
17
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params)
-
end
-
-
1
def private_read?
-
52
access_token? %w[private:read]
-
end
-
-
1
def private_modify?
-
11
access_token? %w[private:modify]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class CommentsController < Api::V1::BaseController
-
1
before_action except: %i[create destroy] do
-
13
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
12
require_access_token %w[interactions public:read]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
7
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
rescue_from ActiveRecord::RecordInvalid do
-
1
render_error 422, "User is not allowed to comment"
-
end
-
-
1
def create
-
5
find_post
-
3
comment = comment_service.create(params.require(:post_id), params.require(:body))
-
rescue ActiveRecord::RecordNotFound
-
2
render_error 404, "Post with provided guid could not be found"
-
else
-
2
render json: comment_as_json(comment), status: :created
-
end
-
-
1
def index
-
5
find_post
-
3
comments_query = comment_service.find_for_post(params.require(:post_id))
-
3
params[:after] = Time.utc(1900).iso8601 if params.permit(:before, :after).empty?
-
-
3
comments_page = time_pager(comments_query).response
-
9
comments_page[:data] = comments_page[:data].map {|x| comment_as_json(x) }
-
3
render_paged_api_response comments_page
-
end
-
-
1
def destroy
-
6
find_post
-
4
if comment_and_post_validate(params.require(:post_id), params[:id])
-
2
comment_service.destroy!(params[:id])
-
1
head :no_content
-
end
-
rescue ActiveRecord::RecordInvalid
-
1
render_error 403, "User not allowed to delete the comment"
-
end
-
-
1
def report
-
8
find_post
-
5
post_guid = params.require(:post_id)
-
5
comment_guid = params.require(:comment_id)
-
5
return unless comment_and_post_validate(post_guid, comment_guid)
-
-
3
reason = params.require(:reason)
-
3
comment = comment_service.find!(comment_guid)
-
3
report = current_user.reports.new(
-
item_id: comment.id,
-
item_type: "Comment",
-
text: reason
-
)
-
3
if report.save
-
2
head :no_content
-
else
-
1
render_error 409, "This item already has been reported by this user"
-
end
-
end
-
-
1
private
-
-
1
def comment_and_post_validate(post_guid, comment_guid)
-
9
if !comment_exists(comment_guid)
-
2
render_error 404, "Comment not found for the given post"
-
2
false
-
7
elsif !comment_is_for_post(post_guid, comment_guid)
-
2
render_error 404, "Comment not found for the given post"
-
2
false
-
else
-
5
true
-
end
-
end
-
-
1
def comment_is_for_post(post_guid, comment_guid)
-
7
comments = comment_service.find_for_post(post_guid)
-
15
comment = comments.find {|comment| comment[:guid] == comment_guid }
-
7
comment ? true : false
-
end
-
-
1
def comment_exists(comment_guid)
-
9
comment = comment_service.find!(comment_guid)
-
7
comment ? true : false
-
rescue ActiveRecord::RecordNotFound
-
2
false
-
end
-
-
1
def comment_service
-
27
@comment_service ||= CommentService.new(current_user)
-
end
-
-
1
def post_service
-
24
@post_service ||= PostService.new(current_user)
-
end
-
-
1
def comment_as_json(comment)
-
8
CommentPresenter.new(comment, current_user).as_api_response
-
end
-
-
1
def find_post
-
24
post = post_service.find!(params[:post_id])
-
19
return post if post.public? || private_read?
-
-
4
raise ActiveRecord::RecordNotFound
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "api/paging/index_paginator"
-
-
1
module Api
-
1
module V1
-
1
class ContactsController < Api::V1::BaseController
-
1
before_action except: %i[create destroy] do
-
5
require_access_token %w[contacts:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
12
require_access_token %w[contacts:modify]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
4
render_error 404, "Aspect with provided ID could not be found"
-
end
-
-
1
def index
-
4
contacts_query = aspects_membership_service.contacts_in_aspect(params.require(:aspect_id))
-
2
contacts_page = index_pager(contacts_query).response
-
2
contacts_page[:data] = contacts_page[:data].map do |c|
-
2
ContactPresenter.new(c, current_user).as_api_json_without_contact
-
end
-
2
render_paged_api_response contacts_page
-
end
-
-
1
def create
-
5
aspect_id = params.require(:aspect_id)
-
5
person = Person.find_by(guid: params.require(:person_guid))
-
5
aspect_membership = aspects_membership_service.create(aspect_id, person.id) if person.present?
-
-
2
if aspect_membership
-
1
head :no_content
-
else
-
1
render_error 422, "Failed to add user to aspect"
-
end
-
rescue ActiveRecord::RecordNotUnique
-
1
render_error 422, "Failed to add user to aspect"
-
end
-
-
1
def destroy
-
5
aspect_id = params.require(:aspect_id)
-
5
person = Person.find_by(guid: params[:id])
-
5
result = aspects_membership_service.destroy_by_ids(aspect_id, person.id) if person.present?
-
-
2
if result && result[:success]
-
1
head :no_content
-
else
-
1
render_error 422, "Failed to remove user from aspect"
-
end
-
rescue ActiveRecord::RecordNotFound
-
3
render_error 404, "Aspect or contact on aspect not found"
-
end
-
-
1
def aspects_membership_service
-
12
AspectsMembershipService.new(current_user)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class ConversationsController < Api::V1::BaseController
-
1
include ConversationsHelper
-
-
1
BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
-
-
1
before_action do
-
71
require_access_token %w[conversations]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
5
render_error 404, "Conversation with provided guid could not be found"
-
end
-
-
1
def index
-
4
mapped_params = {}
-
4
mapped_params[:only_after] = params[:only_after] if params.has_key?(:only_after)
-
-
4
mapped_params[:unread] = BOOLEAN_TYPE.cast(params[:only_unread]) if params.has_key?(:only_unread)
-
-
4
conversations_query = conversation_service.all_for_user(mapped_params)
-
4
conversations_page = pager(conversations_query, "conversations.created_at").response
-
12
conversations_page[:data] = conversations_page[:data].map {|x| conversation_as_json(x) }
-
4
render_paged_api_response conversations_page
-
end
-
-
1
def show
-
6
conversation = conversation_service.find!(params[:id])
-
2
render json: conversation_as_json(conversation)
-
end
-
-
1
def create
-
49
params.require(%i[subject body recipients])
-
45
recipients = recipient_ids
-
44
conversation = conversation_service.build(params[:subject], params[:body], recipients)
-
44
raise ActiveRecord::RecordInvalid unless conversation_valid?(conversation, recipients)
-
-
43
conversation.save!
-
43
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, conversation)
-
43
render json: conversation_as_json(conversation), status: :created
-
rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
-
6
render_error 422, "Couldn't accept or process the conversation"
-
end
-
-
1
def update
-
3
read = BOOLEAN_TYPE.cast(params.require(:read))
-
2
conversation = conversation_service.find!(params[:id])
-
2
conversation.update_read_for(current_user, read: read)
-
-
2
render json: conversation_as_json(conversation)
-
rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
-
1
render_error 422, "Couldn't update the conversation"
-
end
-
-
1
def destroy
-
4
conversation = conversation_service.get_visibility(params[:id])
-
3
conversation.destroy!
-
3
head :no_content
-
end
-
-
1
private
-
-
1
def conversation_service
-
60
@conversation_service ||= ConversationService.new(current_user)
-
end
-
-
1
def conversation_as_json(conversation)
-
55
ConversationPresenter.new(conversation, current_user).as_api_json
-
end
-
-
1
def pager(query, sort_field)
-
4
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, sort_field)
-
end
-
-
1
def recipient_ids
-
90
params[:recipients].map {|p| Person.find_from_guid_or_username(id: p).id }
-
end
-
-
1
def conversation_valid?(conversation, recipients)
-
44
conversation.participants.length == (recipients.length + 1)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class LikesController < Api::V1::BaseController
-
1
before_action do
-
35
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
26
require_access_token %w[interactions]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
8
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
rescue_from ActiveRecord::RecordInvalid do
-
4
render_error 422, "User is not allowed to like"
-
end
-
-
1
def show
-
9
post = post_service.find!(params.require(:post_id))
-
7
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
-
-
5
likes_query = find_likes
-
-
5
return unless likes_query
-
-
4
likes_page = index_pager(likes_query).response
-
10
likes_page[:data] = likes_page[:data].map {|x| like_json(x) }
-
4
render_paged_api_response likes_page
-
end
-
-
1
def create
-
11
post = post_service.find!(params.require(:post_id))
-
9
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
-
-
7
if params[:comment_id].present?
-
4
create_for_comment
-
else
-
3
create_for_post
-
end
-
rescue ActiveRecord::RecordInvalid => e
-
4
if e.message == "Validation failed: Target has already been taken"
-
2
return render_error 409, "Like already exists"
-
end
-
-
2
raise
-
end
-
-
1
def destroy
-
11
post = post_service.find!(params.require(:post_id))
-
7
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
-
-
7
if params[:comment_id].present?
-
4
destroy_for_comment
-
else
-
3
destroy_for_post
-
end
-
end
-
-
1
private
-
-
1
def find_likes
-
5
if params[:comment_id].present?
-
3
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
-
-
2
like_service.find_for_comment(params[:comment_id])
-
else
-
2
like_service.find_for_post(params[:post_id])
-
end
-
end
-
-
1
def like_service
-
16
@like_service ||= LikeService.new(current_user)
-
end
-
-
1
def post_service
-
31
@post_service ||= PostService.new(current_user)
-
end
-
-
1
def comment_service
-
11
@comment_service ||= CommentService.new(current_user)
-
end
-
-
1
def like_json(like)
-
6
LikesPresenter.new(like).as_api_json
-
end
-
-
1
def create_for_post
-
3
like_service.create_for_post(params[:post_id])
-
-
2
head :no_content
-
end
-
-
1
def create_for_comment
-
4
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
-
-
3
like_service.create_for_comment(params[:comment_id])
-
-
2
head :no_content
-
end
-
-
1
def destroy_for_post
-
3
if like_service.unlike_post(params[:post_id])
-
2
head :no_content
-
else
-
1
render_error 410, "Like doesn’t exist"
-
end
-
end
-
-
1
def destroy_for_comment
-
4
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
-
-
3
if like_service.unlike_comment(params[:comment_id])
-
2
head :no_content
-
else
-
1
render_error 410, "Like doesn’t exist"
-
end
-
end
-
-
1
def comment_and_post_validate(post_guid, comment_guid)
-
11
if comment_is_for_post(post_guid, comment_guid)
-
8
true
-
else
-
3
render_error 404, "Comment not found for the given post"
-
3
false
-
end
-
end
-
-
1
def comment_is_for_post(post_guid, comment_guid)
-
11
comments = comment_service.find_for_post(post_guid)
-
11
comments.exists?(guid: comment_guid)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class MessagesController < Api::V1::BaseController
-
1
before_action do
-
8
require_access_token %w[conversations]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
1
render_error 404, "Conversation with provided guid could not be found"
-
end
-
-
1
def create
-
4
conversation = conversation_service.find!(params.require(:conversation_id))
-
3
text = params.require(:body)
-
1
message = current_user.build_message(conversation, text: text)
-
1
message.save!
-
1
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, message)
-
1
render json: message_json(message), status: :created
-
rescue ActionController::ParameterMissing
-
2
render_error 422, "Couldn’t accept or process the conversation"
-
end
-
-
1
def index
-
2
conversation = conversation_service.find!(params.require(:conversation_id))
-
2
messages_page = index_pager(conversation.messages).response
-
5
messages_page[:data] = messages_page[:data].map {|x| message_json(x) }
-
2
render_paged_api_response messages_page
-
end
-
-
1
private
-
-
1
def conversation_service
-
6
ConversationService.new(current_user)
-
end
-
-
1
def message_json(message)
-
4
MessagePresenter.new(message).as_api_json
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class NotificationsController < Api::V1::BaseController
-
1
BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
-
-
1
before_action do
-
17
require_access_token %w[notifications]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
1
render_error 404, "Notification with provided guid could not be found"
-
end
-
-
1
def show
-
3
notification = service.get_by_guid(params[:id])
-
-
3
if notification
-
1
render json: NotificationPresenter.new(notification).as_api_json
-
else
-
2
render_error 404, "Notification with provided guid could not be found"
-
end
-
end
-
-
1
def index
-
7
after_date = Date.iso8601(params[:only_after]) if params.has_key?(:only_after)
-
-
6
notifications_query = service.index(BOOLEAN_TYPE.cast(params[:only_unread]), after_date)
-
6
notifications_page = time_pager(notifications_query).response
-
6
notifications_page[:data] = notifications_page[:data].map do |note|
-
9
NotificationPresenter.new(note, default_serializer_options).as_api_json
-
end
-
6
render_paged_api_response notifications_page
-
rescue ArgumentError
-
1
render_error 422, "Could not process the notifications request"
-
end
-
-
1
def update
-
4
read = BOOLEAN_TYPE.cast(params.require(:read))
-
3
if service.update_status_by_guid(params[:id], read)
-
2
head :no_content
-
else
-
render_error 422, "Could not process the notifications request"
-
end
-
rescue ActionController::ParameterMissing
-
1
render_error 422, "Could not process the notifications request"
-
end
-
-
1
private
-
-
1
def service
-
12
@service ||= NotificationService.new(current_user)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class PhotosController < Api::V1::BaseController
-
1
before_action except: %i[create destroy] do
-
8
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
14
require_access_token %w[public:modify]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
5
render_error 404, "Photo with provided guid could not be found"
-
end
-
-
1
def index
-
2
query = if private_read?
-
1
current_user.photos
-
else
-
1
current_user.photos.where(public: true)
-
end
-
2
photos_page = time_pager(query).response
-
5
photos_page[:data] = photos_page[:data].map {|photo| photo_json(photo) }
-
2
render_paged_api_response photos_page
-
end
-
-
1
def show
-
6
photo = photo_service.visible_photo(params.require(:id))
-
6
raise ActiveRecord::RecordNotFound unless photo
-
-
4
raise ActiveRecord::RecordNotFound unless photo.public? || private_read?
-
-
3
render json: photo_json(photo)
-
end
-
-
1
def create
-
7
image = params.require(:image)
-
6
public_photo = params.has_key?(:aspect_ids)
-
6
raise RuntimeError unless public_photo || private_modify?
-
-
6
base_params = params.permit(:aspect_ids, :pending, :set_profile_photo)
-
6
photo = photo_service.create_from_params_and_file(base_params, image)
-
4
raise RuntimeError unless photo
-
-
4
render json: photo_json(photo)
-
rescue CarrierWave::IntegrityError, ActionController::ParameterMissing, RuntimeError
-
3
render_error 422, "Failed to create the photo"
-
end
-
-
1
def destroy
-
3
photo = current_user.photos.where(guid: params[:id]).first
-
3
raise ActiveRecord::RecordNotFound unless photo
-
-
1
raise ActiveRecord::RecordNotFound unless photo.public? || private_modify?
-
-
1
if current_user.retract(photo)
-
1
head :no_content
-
else
-
render_error 422, "Not allowed to delete the photo"
-
end
-
end
-
-
1
private
-
-
1
def photo_service
-
12
@photo_service ||= PhotoService.new(current_user)
-
end
-
-
1
def photo_json(photo)
-
10
PhotoPresenter.new(photo).as_api_json(true)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class PostInteractionsController < Api::V1::BaseController
-
1
include PostsHelper
-
-
1
before_action do
-
42
require_access_token %w[public:read interactions]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
10
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
def subscribe
-
11
post = find_post
-
9
return head :conflict if current_user.participations.find_by(target_id: post.id)
-
-
8
current_user.participate!(post)
-
8
head :no_content
-
rescue ActiveRecord::RecordInvalid
-
render_error 422, "Cannot subscribe to this post"
-
end
-
-
1
def hide
-
9
return render_error(422, "Missing parameter") if params[:hide].nil?
-
-
8
post = find_post
-
6
hidden = current_user.is_shareable_hidden?(post)
-
-
6
if (params[:hide] && !hidden) || (!params[:hide] && hidden)
-
4
current_user.toggle_hidden_shareable(post)
-
4
head :no_content
-
else
-
2
render_error(params[:hide] ? 409 : 410, params[:hide] ? "Post already hidden" : "Post not hidden")
-
end
-
end
-
-
1
def mute
-
5
post = find_post
-
3
participation = current_user.participations.find_by(target_id: post.id)
-
3
return head :gone unless participation
-
-
2
participation.destroy
-
2
head :no_content
-
end
-
-
1
def report
-
6
reason = params.require(:reason)
-
5
post = find_post
-
3
report = current_user.reports.new(
-
item_id: post.id,
-
item_type: "Post",
-
text: reason
-
)
-
3
if report.save
-
2
head :no_content
-
else
-
1
render_error 409, "Failed to create report on this post"
-
end
-
rescue ActionController::ParameterMissing
-
1
render_error 422, "Failed to create report on this post"
-
end
-
-
1
def vote
-
6
post = find_post
-
begin
-
4
poll_vote = poll_service.vote(post.id, params[:poll_answer])
-
rescue ActiveRecord::RecordNotFound
-
# This, but not the find_post above, should return a 422,
-
# we just keep poll_vote nil so it goes into the else below
-
end
-
3
if poll_vote
-
2
head :no_content
-
else
-
1
render_error 422, "Cant vote on this post"
-
end
-
rescue ActiveRecord::RecordInvalid
-
1
render_error 422, "Cant vote on this post"
-
end
-
-
1
private
-
-
1
def post_service
-
35
@post_service ||= PostService.new(current_user)
-
end
-
-
1
def poll_service
-
4
@poll_service ||= PollParticipationService.new(current_user)
-
end
-
-
1
def find_post
-
35
post = post_service.find!(params[:post_id])
-
30
raise ActiveRecord::RecordNotFound unless post.public? || private_read?
-
-
25
post
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class PostsController < Api::V1::BaseController
-
1
include PostsHelper
-
-
1
before_action except: %i[create destroy] do
-
8
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
29
require_access_token %w[public:modify]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
4
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
def show
-
8
post = post_service.find!(params[:id])
-
6
raise ActiveRecord::RecordNotFound unless post.public? || private_read?
-
-
5
render json: post_as_json(post)
-
end
-
-
1
def create
-
23
creation_params = normalized_create_params
-
13
raise StandardError unless creation_params[:public] || private_modify?
-
-
13
@status_message = creation_service.create(creation_params)
-
8
render json: PostPresenter.new(@status_message, current_user).as_api_response
-
rescue StandardError
-
15
render_error 422, "Failed to create the post"
-
end
-
-
1
def destroy
-
4
post_service.destroy(params[:id], private_modify?)
-
1
head :no_content
-
rescue Diaspora::NotMine, Diaspora::NonPublic
-
2
render_error 403, "Not allowed to delete the post"
-
end
-
-
1
private
-
-
1
def normalized_create_params
-
mapped_parameters = {
-
23
status_message: {
-
text: params[:body]
-
},
-
public: params.require(:public),
-
aspect_ids: normalize_aspect_ids(params.permit(aspects: []))
-
}
-
21
add_location_params(mapped_parameters)
-
21
add_poll_params(mapped_parameters)
-
19
add_photo_ids(mapped_parameters)
-
13
mapped_parameters
-
end
-
-
1
def add_location_params(mapped_parameters)
-
21
return unless params.has_key?(:location)
-
-
1
location = params.require(:location)
-
1
mapped_parameters[:location_address] = location[:address]
-
1
mapped_parameters[:location_coords] = "#{location[:lat]},#{location[:lng]}"
-
end
-
-
1
def add_photo_ids(mapped_parameters)
-
19
return unless params.has_key?(:photos)
-
-
8
photo_guids = params[:photos]
-
8
return if photo_guids.empty?
-
-
20
photos = photo_guids.map {|guid| Photo.find_by!(guid: guid) }
-
10
.select {|p| p.author_id == current_user.person.id && p.pending }
-
6
raise InvalidArgument if photos.length != photo_guids.length
-
-
2
mapped_parameters[:photos] = photos
-
end
-
-
1
def add_poll_params(mapped_parameters)
-
21
return unless params.has_key?(:poll)
-
-
4
poll_data = params.require(:poll)
-
4
question = poll_data[:question]
-
4
answers = poll_data[:poll_answers]
-
4
raise InvalidArgument if question.blank?
-
-
4
raise InvalidArgument if answers.empty?
-
-
4
answers.each do |a|
-
8
raise InvalidArgument if a.blank?
-
end
-
2
mapped_parameters[:poll_question] = question
-
2
mapped_parameters[:poll_answers] = answers
-
end
-
-
1
def normalize_aspect_ids(aspects)
-
21
aspects.empty? ? [] : aspects[:aspects]
-
end
-
-
1
def post_service
-
12
@post_service ||= PostService.new(current_user)
-
end
-
-
1
def creation_service
-
13
@creation_service ||= StatusMessageCreationService.new(current_user)
-
end
-
-
1
def post_as_json(post)
-
5
PostPresenter.new(post, current_user).as_api_response
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class ResharesController < Api::V1::BaseController
-
1
before_action except: %i[create] do
-
5
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[create] do
-
6
require_access_token %w[public:modify]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
3
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
rescue_from Diaspora::NonPublic do
-
render_error 404, "Post with provided guid could not be found"
-
end
-
-
1
def show
-
5
reshares_query = reshare_service.find_for_post(params.require(:post_id))
-
2
reshares_page = index_pager(reshares_query).response
-
2
reshares_page[:data] = reshares_page[:data].map do |r|
-
{
-
1
guid: r.guid,
-
created_at: r.created_at,
-
author: PersonPresenter.new(r.author).as_api_json
-
}
-
end
-
2
render_paged_api_response reshares_page
-
end
-
-
1
def create
-
5
reshare = reshare_service.create(params.require(:post_id))
-
rescue ActiveRecord::RecordInvalid
-
1
render_error 409, "Reshare already exists"
-
rescue ActiveRecord::RecordNotFound, RuntimeError
-
3
render_error 422, "Failed to reshare"
-
else
-
1
render json: PostPresenter.new(reshare, current_user).as_api_response
-
end
-
-
1
private
-
-
1
def reshare_service
-
10
@reshare_service ||= ReshareService.new(current_user)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class SearchController < Api::V1::BaseController
-
1
USER_FILTER_CONTACTS = "contacts"
-
1
USER_FILTER_RECEIVING_CONTACTS = "contacts:receiving"
-
1
USER_FILTER_SHARING_CONTACTS = "contacts:sharing"
-
1
USER_FILTER_ASPECTS_PREFIX = "aspect:"
-
1
USER_FILTERS_EXACT_MATCH = [USER_FILTER_CONTACTS, USER_FILTER_RECEIVING_CONTACTS,
-
USER_FILTER_SHARING_CONTACTS].freeze
-
1
USER_FILTERS_PREFIX_MATCH = [USER_FILTER_ASPECTS_PREFIX].freeze
-
-
1
before_action do
-
30
require_access_token %w[public:read]
-
end
-
-
1
rescue_from RuntimeError do |e|
-
6
render_error 422, e.message
-
end
-
-
1
def user_index
-
21
user_page = index_pager(people_query).response
-
32
user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json }
-
13
render_paged_api_response user_page
-
end
-
-
1
def post_index
-
6
posts_page = time_pager(posts_query, "posts.created_at", "created_at").response
-
14
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response }
-
5
render_paged_api_response posts_page
-
end
-
-
1
def tag_index
-
3
tags_page = index_pager(tags_query).response
-
2
tags_page[:data] = tags_page[:data].pluck(:name)
-
2
render_paged_api_response tags_page
-
end
-
-
1
private
-
-
1
def time_pager(query, query_time_field, data_time_field)
-
5
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, query_time_field, data_time_field)
-
end
-
-
1
def people_query
-
21
tag = params[:tag]
-
21
name_or_handle = params[:name_or_handle]
-
21
raise "Parameters tag and name_or_handle are exclusive" if tag.present? && name_or_handle.present?
-
-
20
query = if tag.present?
-
# scope filters to only searchable people already
-
1
Person.profile_tagged_with(tag)
-
19
elsif name_or_handle.present?
-
18
Person.searchable(contacts_read? && current_user) # rubocop:disable Rails/DynamicFindBy
-
.find_by_substring(name_or_handle)
-
else
-
1
raise "Missing parameter tag or name_or_handle"
-
end
-
-
19
query = query.where(closed_account: false)
-
-
19
user_filters.each do |filter|
-
11
query = query.contacts_of(current_user) if filter == USER_FILTER_CONTACTS
-
-
11
if filter == USER_FILTER_RECEIVING_CONTACTS
-
2
query = query.contacts_of(current_user).where(contacts: {receiving: true})
-
end
-
-
11
if filter == USER_FILTER_SHARING_CONTACTS
-
2
query = query.contacts_of(current_user).where(contacts: {sharing: true})
-
end
-
-
11
if filter.start_with?(USER_FILTER_ASPECTS_PREFIX) # rubocop:disable Style/Next
-
6
_, ids = filter.split(":", 2)
-
6
ids = ids.split(",").map {|id|
-
8
Integer(id) rescue raise("Invalid aspect filter") # rubocop:disable Style/RescueModifier
-
}
-
-
6
raise "Invalid aspect filter" unless current_user.aspects.where(id: ids).count == ids.size
-
-
4
query = Person.where(id: query.all_from_aspects(ids, current_user).select(:id))
-
end
-
end
-
-
13
query.distinct
-
end
-
-
1
def posts_query
-
6
opts = {}
-
6
opts[:public_only] = !private_read?
-
6
Stream::Tag.new(current_user, params.require(:tag), opts).stream_posts
-
end
-
-
1
def tags_query
-
3
ActsAsTaggableOn::Tag.autocomplete(params.require(:query))
-
end
-
-
1
def user_filters
-
19
@user_filters ||= Array(params[:filter]).uniq.tap do |filters|
-
19
raise "Invalid filter" unless filters.all? {|filter|
-
15
USER_FILTERS_EXACT_MATCH.include?(filter) ||
-
9
USER_FILTERS_PREFIX_MATCH.any? {|prefix| filter.start_with?(prefix) }
-
}
-
-
# For now all filters require contacts:read
-
17
require_access_token %w[contacts:read] unless filters.empty?
-
end
-
end
-
-
1
def contacts_read?
-
18
access_token? %w[contacts:read]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class StreamsController < Api::V1::BaseController
-
1
before_action do
-
23
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %w[aspects] do
-
5
require_access_token %w[contacts:read private:read]
-
end
-
-
1
before_action only: %w[followed_tags] do
-
3
require_access_token %w[tags:read]
-
end
-
-
1
def aspects
-
4
aspect_ids = params.has_key?(:aspect_ids) ? JSON.parse(params[:aspect_ids]) : []
-
4
@stream = Stream::Aspect.new(current_user, aspect_ids, max_time: stream_max_time)
-
4
stream_responder
-
end
-
-
1
def activity
-
3
stream_responder(Stream::Activity, "posts.interacted_at", "interacted_at")
-
end
-
-
1
def multi
-
3
stream_responder(Stream::Multi)
-
end
-
-
1
def commented
-
3
stream_responder(Stream::Comments)
-
end
-
-
1
def liked
-
3
stream_responder(Stream::Likes)
-
end
-
-
1
def mentions
-
3
stream_responder(Stream::Mention)
-
end
-
-
1
def followed_tags
-
2
stream_responder(Stream::FollowedTag)
-
end
-
-
1
private
-
-
1
def stream_responder(stream_klass=nil, query_time_field="posts.created_at", data_time_field="created_at")
-
21
@stream = stream_klass.present? ? stream_klass.new(current_user, max_time: stream_max_time) : @stream
-
21
query = @stream.stream_posts
-
21
query = query.where(public: true) unless private_read?
-
21
posts_page = pager(query, query_time_field, data_time_field).response
-
65
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
-
21
posts_page[:links].delete(:previous)
-
21
render_paged_api_response posts_page
-
end
-
-
1
def stream_max_time
-
21
if params.has_key?("before")
-
Time.iso8601(params["before"])
-
else
-
21
max_time
-
end
-
end
-
-
1
def pager(query, query_time_field, data_time_field)
-
21
Api::Paging::RestPaginatorBuilder.new(query, request, true, 15)
-
.time_pager(params, query_time_field, data_time_field)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class TagFollowingsController < Api::V1::BaseController
-
1
before_action except: %i[create destroy] do
-
5
require_access_token %w[tags:read]
-
end
-
-
1
before_action only: %i[create destroy] do
-
7
require_access_token %w[tags:modify]
-
end
-
-
1
def index
-
4
render json: tag_followings_service.index.pluck(:name)
-
end
-
-
1
def create
-
3
tag_followings_service.create(params.require(:name))
-
1
head :no_content
-
rescue TagFollowingService::DuplicateTag
-
1
render_error 409, "Already following this tag"
-
rescue StandardError
-
1
render_error 422, "Failed to process the tag followings request"
-
end
-
-
1
def destroy
-
2
tag_followings_service.destroy_by_name(params.require(:id))
-
1
head :no_content
-
rescue ActiveRecord::RecordNotFound
-
1
render_error 410, "Not following this tag"
-
end
-
-
1
private
-
-
1
def tag_followings_service
-
9
@tag_followings_service ||= TagFollowingService.new(current_user)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module V1
-
1
class UsersController < Api::V1::BaseController
-
1
include TagsHelper
-
-
1
before_action except: %i[contacts update show] do
-
17
require_access_token %w[public:read]
-
end
-
-
1
before_action only: %i[update] do
-
5
require_access_token %w[profile:modify]
-
end
-
-
1
before_action only: %i[contacts] do
-
5
require_access_token %w[contacts:read]
-
end
-
-
1
before_action only: %i[block] do
-
9
require_access_token %w[contacts:modify]
-
end
-
-
1
before_action only: %i[show] do
-
7
require_access_token %w[profile]
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
5
render_error 404, "User not found"
-
end
-
-
1
def show
-
6
person = if params.has_key?(:id)
-
5
found_person = Person.find_by!(guid: params[:id])
-
4
raise ActiveRecord::RecordNotFound unless found_person.searchable || access_token?("contacts:read")
-
-
4
found_person
-
else
-
1
current_user.person
-
end
-
5
render json: PersonPresenter.new(person, current_user).profile_hash_as_api_json
-
end
-
-
1
def update
-
4
params_to_update = profile_update_params
-
4
if params_to_update && current_user.update_profile(params_to_update)
-
4
render json: PersonPresenter.new(current_user.person, current_user).profile_hash_as_api_json
-
else
-
render_error 422, "Failed to update the user settings"
-
end
-
rescue RuntimeError
-
render_error 422, "Failed to update the user settings"
-
end
-
-
1
def contacts
-
4
if params.require(:user_id) != current_user.guid
-
2
render_error 404, "User not found"
-
2
return
-
end
-
-
2
contacts_query = aspects_service.all_contacts
-
2
contacts_page = index_pager(contacts_query).response
-
3
contacts_page[:data] = contacts_page[:data].map {|c| PersonPresenter.new(c.person).as_api_json }
-
2
render_paged_api_response contacts_page
-
end
-
-
1
def photos
-
4
person = Person.find_by!(guid: params[:user_id])
-
3
user_for_query = current_user if private_read?
-
3
photos_query = Photo.visible(user_for_query, person, :all, Time.current)
-
3
photos_page = time_pager(photos_query).response
-
10
photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
-
3
render_paged_api_response photos_page
-
end
-
-
1
def posts
-
4
person = Person.find_by!(guid: params[:user_id])
-
3
posts_query = if private_read?
-
2
current_user.posts_from(person, false)
-
else
-
1
Post.where(author_id: person.id, public: true)
-
end
-
3
posts_page = time_pager(posts_query).response
-
11
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
-
3
render_paged_api_response posts_page
-
end
-
-
1
def block
-
7
person = Person.find_by!(guid: params[:user_id])
-
5
service = BlockService.new(current_user)
-
5
if request.request_method_symbol == :post
-
begin
-
3
service.block(person)
-
2
head :created
-
rescue ActiveRecord::RecordNotUnique
-
1
render_error 409, "User is already blocked"
-
end
-
2
elsif request.request_method_symbol == :delete
-
begin
-
2
service.unblock(person)
-
1
head :no_content
-
rescue ActiveRecord::RecordNotFound
-
1
render_error 410, "User is not blocked"
-
end
-
else
-
raise AbstractController::ActionNotFound
-
end
-
end
-
-
1
private
-
-
1
def aspects_service
-
2
@aspects_service ||= AspectsMembershipService.new(current_user)
-
end
-
-
1
def profile_update_params
-
4
raise RuntimeError if params.has_key?(:id)
-
-
4
updates = params.permit(:bio, :birthday, :gender, :location, :name,
-
:searchable, :show_profile_info, :nsfw, :tags).to_h || {}
-
4
if updates.has_key?(:name)
-
1
updates[:first_name] = updates[:name]
-
1
updates[:last_name] = nil
-
1
updates.delete(:name)
-
end
-
4
if updates.has_key?(:show_profile_info)
-
1
updates[:public_details] = updates[:show_profile_info]
-
1
updates.delete(:show_profile_info)
-
end
-
4
process_tags_updates(updates)
-
4
updates
-
end
-
-
1
def process_tags_updates(updates)
-
4
return unless params.has_key?(:tags)
-
-
1
raise RuntimeError if params[:tags].length > Profile::MAX_TAGS
-
-
3
tags = params[:tags].map {|tag| "#" + normalize_tag_name(tag) }.join(" ")
-
1
updates[:tag_string] = tags
-
1
updates.delete(:tags)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ApplicationController < ActionController::Base
-
1
before_action :force_tablet_html
-
1
has_mobile_fu
-
-
1
rescue_from ActionController::InvalidAuthenticityToken do
-
3
if user_signed_in?
-
3
logger.warn "#{current_user.diaspora_handle} CSRF token fail. referer: #{request.referer || 'empty'}"
-
3
Workers::Mail::CsrfTokenFail.perform_async(current_user.id)
-
3
sign_out current_user
-
end
-
3
flash[:error] = I18n.t("error_messages.csrf_token_fail")
-
3
redirect_to new_user_session_path format: request[:format]
-
end
-
-
1
before_action :ensure_http_referer_is_set
-
1
before_action :set_locale
-
1
before_action :set_diaspora_header
-
1
before_action :mobile_switch
-
1
before_action :gon_set_current_user
-
1
before_action :gon_set_appconfig
-
1
before_action :gon_set_preloads
-
1
before_action :configure_permitted_parameters, if: :devise_controller?
-
-
1
helper_method :all_aspects,
-
:all_contacts_count,
-
:my_contacts_count,
-
:only_sharing_count,
-
:tag_followings,
-
:tags,
-
:open_publisher
-
-
185
layout proc { request.format == :mobile ? "application" : "with_header_with_footer" }
-
-
1
private
-
-
1
def default_serializer_options
-
522
{root: false}
-
end
-
-
1
def ensure_http_referer_is_set
-
1185
request.env["HTTP_REFERER"] ||= "/"
-
end
-
-
# Overwriting the sign_out redirect path method
-
1
def after_sign_out_path_for(resource_or_scope)
-
3
is_mobile_device? ? root_path : new_user_session_path
-
end
-
-
1
def all_aspects
-
564
@all_aspects ||= current_user.aspects
-
end
-
-
1
def all_contacts_count
-
9
@all_contacts_count ||= current_user.contacts.count
-
end
-
-
1
def my_contacts_count
-
9
@my_contacts_count ||= current_user.contacts.receiving.count
-
end
-
-
1
def only_sharing_count
-
9
@only_sharing_count ||= current_user.contacts.only_sharing.count
-
end
-
-
1
def tags
-
11
@tags ||= current_user.followed_tags
-
end
-
-
1
def ensure_page
-
13
params[:page] = params[:page] ? params[:page].to_i : 1
-
end
-
-
1
def set_diaspora_header
-
1185
headers["X-Diaspora-Version"] = AppConfig.version_string
-
-
1185
if AppConfig.git_available?
-
1185
headers["X-Git-Update"] = AppConfig.git_update if AppConfig.git_update.present?
-
1185
headers["X-Git-Revision"] = AppConfig.git_revision if AppConfig.git_revision.present?
-
end
-
end
-
-
1
def set_locale
-
1185
if user_signed_in?
-
621
I18n.locale = current_user.language
-
else
-
564
locale = http_accept_language.language_region_compatible_from AVAILABLE_LANGUAGE_CODES
-
564
locale ||= DEFAULT_LANGUAGE
-
564
I18n.locale = locale
-
end
-
end
-
-
1
def redirect_unless_admin
-
26
return if current_user.admin?
-
3
redirect_to stream_url, notice: "you need to be an admin to do that"
-
end
-
-
1
def redirect_unless_moderator
-
15
return if current_user.moderator?
-
5
redirect_to stream_url, notice: "you need to be an admin or moderator to do that"
-
end
-
-
# use :mobile view for mobile and :html for everything else
-
# (except if explicitly specified, e.g. :json, :xml)
-
1
def mobile_switch
-
1185
if session[:mobile_view] == true && request.format.html?
-
10
request.format = :mobile
-
end
-
end
-
-
1
def force_tablet_html
-
1185
session[:tablet_view] = false
-
end
-
-
1
def after_sign_in_path_for(resource)
-
13
stored_location_for(:user) || current_user_redirect_path
-
end
-
-
1
def max_time
-
85
params[:max_time] ? Time.at(params[:max_time].to_i) : Time.now + 1
-
end
-
-
1
def current_user_redirect_path
-
# If getting started is active AND the user has not completed the getting_started page
-
13
if current_user.getting_started? && !current_user.basic_profile_present?
-
8
getting_started_path
-
else
-
5
stream_path
-
end
-
end
-
-
1
def gon_set_appconfig
-
1185
gon.push(appConfig: {
-
settings: {podname: AppConfig.settings.pod_name},
-
map: {mapbox: {
-
enabled: AppConfig.map.mapbox.enabled?,
-
access_token: AppConfig.map.mapbox.access_token,
-
style: AppConfig.map.mapbox.style
-
}}
-
})
-
end
-
-
1
def gon_set_current_user
-
1185
return unless user_signed_in?
-
621
a_ids = session[:a_ids] || []
-
621
user = UserPresenter.new(current_user, a_ids)
-
621
gon.push(user: user)
-
end
-
-
1
def gon_set_preloads
-
1185
return unless gon.preloads.nil?
-
1156
gon.preloads = {}
-
end
-
-
1
protected
-
-
1
def configure_permitted_parameters
-
30
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
class AspectMembershipsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
respond_to :json
-
-
1
def destroy
-
2
delete_results = AspectsMembershipService.new(current_user).destroy_by_membership_id(params[:id])
-
1
success = delete_results[:success]
-
1
membership = delete_results[:membership]
-
-
# set the flash message
-
1
respond_to do |format|
-
1
format.json do
-
1
if success
-
1
render json: AspectMembershipPresenter.new(membership).base_hash
-
else
-
render plain: membership.errors.full_messages, status: 403
-
end
-
end
-
end
-
end
-
-
1
def create
-
7
aspect_membership = AspectsMembershipService.new(current_user).create(params[:aspect_id], params[:person_id])
-
-
5
if aspect_membership
-
5
respond_to do |format|
-
5
format.json do
-
5
render json: AspectMembershipPresenter.new(aspect_membership).base_hash
-
end
-
end
-
else
-
respond_to do |format|
-
format.json do
-
render plain: I18n.t("aspects.add_to_aspect.failure"), status: 409
-
end
-
end
-
end
-
rescue RuntimeError
-
1
respond_to do |format|
-
1
format.json do
-
1
render plain: I18n.t("aspects.add_to_aspect.failure"), status: :conflict
-
end
-
end
-
end
-
-
1
rescue_from ActiveRecord::StatementInvalid do
-
1
render plain: I18n.t("aspect_memberships.destroy.invalid_statement"), status: 400
-
end
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
1
render plain: I18n.t("aspect_memberships.destroy.no_membership"), status: 404
-
end
-
-
1
rescue_from Diaspora::NotMine do
-
render plain: I18n.t("aspect_memberships.destroy.forbidden"), status: 403
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class AspectsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
respond_to :html,
-
:js,
-
:json
-
-
1
def create
-
7
@aspect = current_user.aspects.build(aspect_params)
-
7
aspecting_person_id = params[:person_id]
-
-
7
if @aspect.save
-
5
result = {id: @aspect.id, name: @aspect.name}
-
5
if aspecting_person_id.present?
-
3
aspect_membership = connect_person_to_aspect(aspecting_person_id)
-
3
result[:aspect_membership] = AspectMembershipPresenter.new(aspect_membership).base_hash if aspect_membership
-
end
-
-
5
render json: result
-
else
-
2
head :unprocessable_entity
-
end
-
end
-
-
1
def destroy
-
begin
-
5
if current_user.auto_follow_back && aspect.id == current_user.auto_follow_back_aspect.id
-
3
current_user.update(auto_follow_back: false, auto_follow_back_aspect: nil)
-
3
flash[:notice] = I18n.t "aspects.destroy.success_auto_follow_back", name: aspect.name
-
else
-
2
flash[:notice] = I18n.t "aspects.destroy.success", name: aspect.name
-
end
-
5
aspect.destroy
-
rescue ActiveRecord::StatementInvalid => e
-
flash[:error] = I18n.t "aspects.destroy.failure", name: aspect.name
-
end
-
-
5
if request.referer.include?("contacts")
-
redirect_to contacts_path
-
else
-
5
redirect_to aspects_path
-
end
-
end
-
-
1
def show
-
2
if aspect
-
1
redirect_to aspects_path("a_ids[]" => aspect.id)
-
else
-
1
redirect_to aspects_path
-
end
-
end
-
-
1
def update
-
2
if aspect.update!(aspect_params)
-
2
flash[:notice] = I18n.t "aspects.update.success", name: aspect.name
-
else
-
flash[:error] = I18n.t "aspects.update.failure", name: aspect.name
-
end
-
2
render json: {id: aspect.id, name: aspect.name}
-
end
-
-
1
def update_order
-
1
params[:ordered_aspect_ids].each_with_index do |id, i|
-
2
current_user.aspects.find(id).update(order_id: i)
-
end
-
1
head :no_content
-
end
-
-
1
private
-
-
1
def aspect
-
24
@aspect ||= current_user.aspects.where(id: params[:id]).first
-
end
-
-
1
def connect_person_to_aspect(aspecting_person_id)
-
3
@person = Person.find(aspecting_person_id)
-
3
if @contact = current_user.contact_for(@person)
-
1
@contact.aspect_memberships.create(aspect: @aspect)
-
else
-
2
@contact = current_user.share_with(@person, @aspect)
-
2
@contact.aspect_memberships.first
-
end
-
end
-
-
1
def aspect_params
-
9
params.require(:aspect).permit(:name, :order_id)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class BlocksController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def create
-
begin
-
2
block_service.block(Person.find_by!(id: block_params[:person_id]))
-
rescue ActiveRecord::RecordNotUnique
-
end
-
-
2
respond_to do |format|
-
4
format.json { head :no_content }
-
2
format.any { redirect_back fallback_location: root_path }
-
end
-
end
-
-
1
def destroy
-
7
notice = nil
-
begin
-
7
block_service.remove_block(current_user.blocks.find_by!(id: params[:id]))
-
6
notice = {notice: t("blocks.destroy.success")}
-
rescue ActiveRecord::RecordNotFound
-
1
notice = {error: t("blocks.destroy.failure")}
-
end
-
-
7
respond_to do |format|
-
9
format.json { head :no_content }
-
12
format.any { redirect_back fallback_location: privacy_settings_path, flash: notice }
-
end
-
end
-
-
1
private
-
-
1
def block_params
-
2
params.require(:block).permit(:person_id)
-
end
-
-
1
def block_service
-
9
BlockService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class CommentsController < ApplicationController
-
1
before_action :authenticate_user!, except: :index
-
-
1
respond_to :html, :mobile, :json
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
3
head :not_found
-
end
-
-
1
rescue_from Diaspora::NonPublic do
-
1
authenticate_user!
-
end
-
-
1
def index
-
5
comments = comment_service.find_for_post(params[:post_id])
-
2
respond_with do |format|
-
3
format.json { render json: CommentPresenter.as_collection(comments, :as_json, current_user), status: :ok }
-
3
format.mobile { render layout: false, locals: {comments: comments} }
-
end
-
end
-
-
1
def new
-
respond_to do |format|
-
format.mobile { render layout: false }
-
end
-
end
-
-
1
def create
-
begin
-
6
comment = comment_service.create(params[:post_id], params[:text])
-
rescue ActiveRecord::RecordNotFound
-
1
render plain: I18n.t("comments.create.error"), status: :not_found
-
1
return
-
end
-
-
5
if comment
-
5
respond_create_success(comment)
-
else
-
render plain: I18n.t("comments.create.error"), status: :unprocessable_entity
-
end
-
end
-
-
1
def destroy
-
5
if comment_service.destroy(params[:id])
-
3
respond_destroy_success
-
else
-
1
respond_destroy_error
-
end
-
end
-
-
1
private
-
-
1
def comment_service
-
16
@comment_service ||= CommentService.new(current_user)
-
end
-
-
1
def respond_create_success(comment)
-
5
respond_to do |format|
-
6
format.json { render json: CommentPresenter.new(comment), status: 201 }
-
8
format.html { head :created }
-
6
format.mobile { render partial: "comment", locals: {comment: comment} }
-
end
-
end
-
-
1
def respond_destroy_success
-
3
respond_to do |format|
-
3
format.mobile { redirect_back fallback_location: stream_path }
-
6
format.js { head :no_content }
-
3
format.json { head :no_content }
-
end
-
end
-
-
1
def respond_destroy_error
-
1
respond_to do |format|
-
1
format.mobile { redirect_back fallback_location: stream_path }
-
2
format.js { head :forbidden }
-
1
format.json { head :forbidden }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ContactsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def index
-
22
respond_to do |format|
-
-
# Used for normal requests to contacts#index
-
29
format.html { set_up_contacts }
-
-
# Used by the mobile site
-
23
format.mobile { set_up_contacts_mobile }
-
-
# Used for mentions in the publisher and pagination on the contacts page
-
22
format.json {
-
14
@people = if params[:q].present?
-
6
mutual = params[:mutual].present? && params[:mutual]
-
6
Person.search(params[:q], current_user, only_contacts: true, mutual: mutual).limit(15)
-
else
-
8
set_up_contacts_json
-
end
-
14
render json: @people
-
}
-
end
-
end
-
-
1
def spotlight
-
2
@spotlight = true
-
2
@people = Person.community_spotlight
-
end
-
-
1
private
-
-
1
def set_up_contacts
-
7
if params[:a_id].present?
-
1
@aspect = current_user.aspects.find(params[:a_id])
-
1
gon.preloads[:aspect] = AspectPresenter.new(@aspect).as_json
-
end
-
7
@contacts_size = current_user.contacts.size
-
end
-
-
1
def set_up_contacts_json
-
8
type = params[:set].presence
-
8
if params[:a_id].present?
-
4
type ||= "by_aspect"
-
4
@aspect = current_user.aspects.find(params[:a_id])
-
end
-
8
type ||= "receiving"
-
8
contacts_by_type(type).paginate(page: params[:page], per_page: 25)
-
19
.map {|c| ContactPresenter.new(c, current_user).full_hash_with_person }
-
end
-
-
1
def contacts_by_type(type)
-
8
order = ["profiles.first_name ASC", "profiles.last_name ASC", "profiles.diaspora_handle ASC"]
-
8
contacts = case type
-
when "all"
-
2
order.unshift "receiving DESC"
-
2
current_user.contacts
-
when "only_sharing"
-
current_user.contacts.only_sharing
-
when "receiving"
-
2
current_user.contacts.receiving
-
when "by_aspect"
-
4
order.unshift Arel.sql("contact_id IS NOT NULL DESC")
-
4
contacts_by_aspect(@aspect.id)
-
else
-
raise ArgumentError, "unknown type #{type}"
-
end
-
8
contacts.includes(person: :profile)
-
.order(order)
-
end
-
-
1
def contacts_by_aspect(aspect_id)
-
4
contacts = current_user.contacts.arel_table
-
4
aspect_memberships = AspectMembership.arel_table
-
4
current_user.contacts.joins(
-
contacts.outer_join(aspect_memberships).on(
-
aspect_memberships[:aspect_id].eq(aspect_id).and(
-
aspect_memberships[:contact_id].eq(contacts[:id])
-
)
-
).join_sources
-
)
-
end
-
-
1
def set_up_contacts_mobile
-
1
@contacts = case params[:set]
-
when "only_sharing"
-
current_user.contacts.only_sharing
-
when "all"
-
current_user.contacts
-
else
-
1
if params[:a_id]
-
@aspect = current_user.aspects.find(params[:a_id])
-
@aspect.contacts
-
else
-
1
current_user.contacts.receiving
-
end
-
end
-
1
@contacts = @contacts.for_a_stream.paginate(:page => params[:page], :per_page => 25)
-
1
@contacts_size = @contacts.length
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
class ConversationVisibilitiesController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def destroy
-
5
@vis = ConversationVisibility.where(:person_id => current_user.person.id,
-
:conversation_id => params[:conversation_id]).first
-
5
if @vis
-
4
participants = @vis.conversation.participants.count
-
4
if @vis.destroy
-
4
if participants == 1
-
1
flash[:notice] = I18n.t('conversations.destroy.delete_success')
-
else
-
3
flash[:notice] = I18n.t('conversations.destroy.hide_success')
-
end
-
end
-
end
-
5
redirect_to conversations_path
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ConversationsController < ApplicationController
-
1
before_action :authenticate_user!
-
1
respond_to :html, :mobile, :json, :js
-
-
1
def index
-
8
@visibilities = ConversationVisibility.includes(:conversation)
-
.order("conversations.updated_at DESC")
-
.where(person_id: current_user.person_id)
-
.paginate(page: params[:page], per_page: 15)
-
-
8
if params[:conversation_id]
-
4
@conversation = Conversation.joins(:conversation_visibilities)
-
.where(conversation_visibilities: {
-
person_id: current_user.person_id,
-
conversation_id: params[:conversation_id]
-
}).first
-
-
4
if @conversation
-
3
@first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
-
3
@conversation.set_read(current_user)
-
end
-
end
-
-
8
gon.contacts = contacts_data
-
-
8
respond_with do |format|
-
15
format.html { render "index", locals: {no_contacts: current_user.contacts.mutual.empty?} }
-
9
format.json { render json: @visibilities.map(&:conversation), status: 200 }
-
end
-
end
-
-
1
def create
-
# Contacts autocomplete does not work the same way on mobile and desktop
-
# Mobile returns contact ids array while desktop returns person id
-
# This will have to be removed when mobile autocomplete is ported to Typeahead
-
106
recipients_param, column = [%i(contact_ids id), %i(person_ids person_id)].find {|param, _| params[param].present? }
-
40
if recipients_param
-
28
person_ids = current_user.contacts.mutual.where(column => params[recipients_param].split(",")).pluck(:person_id)
-
end
-
-
40
unless person_ids.present?
-
18
render plain: I18n.t("javascripts.conversation.create.no_recipient"), status: 422
-
18
return
-
end
-
-
22
opts = params.require(:conversation).permit(:subject)
-
22
opts[:participant_ids] = person_ids
-
22
opts[:message] = { text: params[:conversation][:text] }
-
22
@conversation = current_user.build_conversation(opts)
-
-
22
if @conversation.save
-
16
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, @conversation)
-
16
flash[:notice] = I18n.t("conversations.create.sent")
-
16
render json: {id: @conversation.id}
-
else
-
6
render plain: I18n.t("conversations.create.fail"), status: 422
-
end
-
end
-
-
1
def show
-
2
respond_to do |format|
-
2
format.html do
-
1
redirect_to conversations_path(conversation_id: params[:id])
-
1
return
-
end
-
-
2
if @conversation = current_user.conversations.where(id: params[:id]).first
-
2
@first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
-
2
@conversation.set_read(current_user)
-
-
3
format.json { render :json => @conversation, :status => 200 }
-
else
-
redirect_to conversations_path
-
end
-
end
-
end
-
-
1
def raw
-
2
@conversation = current_user.conversations.where(id: params[:conversation_id]).first
-
2
if @conversation
-
1
@first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
-
1
@conversation.set_read(current_user)
-
1
render partial: "conversations/show", locals: {conversation: @conversation}
-
else
-
1
head :not_found
-
end
-
end
-
-
1
def new
-
7
if !params[:modal] && !session[:mobile_view] && request.format.html?
-
1
redirect_to conversations_path
-
1
return
-
end
-
-
6
if session[:mobile_view] == true && request.format.html?
-
5
@contacts_json = contacts_data.to_json
-
-
5
@contact_ids = if params[:contact_id]
-
current_user.contacts.find(params[:contact_id]).id
-
5
elsif params[:aspect_id]
-
current_user.aspects.find(params[:aspect_id]).contacts.pluck(:id).join(",")
-
end
-
-
5
render :layout => true
-
else
-
1
render :layout => false
-
end
-
end
-
-
1
private
-
-
1
def contacts_data
-
13
current_user.contacts.mutual.joins(person: :profile)
-
.pluck(*%w(contacts.id profiles.first_name profiles.last_name people.diaspora_handle))
-
.map {|contact_id, *name_attrs|
-
13
{value: contact_id, name: ERB::Util.h(Person.name_from_attrs(*name_attrs)) }
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class HelpController < ApplicationController
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class HomeController < ApplicationController
-
1
def show
-
4
partial_dir = Rails.root.join("app", "views", "home")
-
4
if user_signed_in?
-
1
redirect_to stream_path
-
3
elsif request.format == :mobile
-
1
if partial_dir.join("_show.mobile.haml").exist? ||
-
partial_dir.join("_show.mobile.erb").exist? ||
-
partial_dir.join("_show.haml").exist?
-
render :show
-
else
-
1
redirect_to user_session_path
-
end
-
2
elsif partial_dir.join("_show.html.haml").exist? ||
-
partial_dir.join("_show.html.erb").exist? ||
-
partial_dir.join("_show.haml").exist?
-
render :show
-
2
elsif Role.admins.any?
-
1
render :default
-
else
-
1
redirect_to podmin_path
-
end
-
end
-
-
1
def podmin
-
2
render :podmin
-
end
-
-
1
def toggle_mobile
-
2
session[:mobile_view] = session[:mobile_view].nil? ? true : !session[:mobile_view]
-
-
2
redirect_back fallback_location: root_path
-
end
-
-
1
def force_mobile
-
2
session[:mobile_view] = true
-
-
2
redirect_to stream_path
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class InvitationCodesController < ApplicationController
-
1
before_action :ensure_valid_invite_code
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
1
redirect_to root_url, :notice => I18n.t('invitation_codes.not_valid')
-
end
-
-
1
def show
-
2
if user_signed_in?
-
1
invite = InvitationCode.find_by_token!(params[:id])
-
1
flash[:notice] = I18n.t("invitation_codes.already_logged_in", inviter: invite.user.name)
-
1
redirect_to person_path(invite.user.person)
-
else
-
1
redirect_to new_user_registration_path(invite: {token: params[:id]})
-
end
-
end
-
-
1
private
-
-
1
def ensure_valid_invite_code
-
3
InvitationCode.find_by_token!(params[:id])
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class InvitationsController < ApplicationController
-
1
before_action :authenticate_user!
-
1
before_action :check_invitations_available!, only: :create
-
-
1
def new
-
1
@invite_code = current_user.invitation_code
-
-
1
@invalid_emails = html_safe_string_from_session_array(:invalid_email_invites)
-
1
@valid_emails = html_safe_string_from_session_array(:valid_email_invites)
-
-
1
respond_to do |format|
-
1
format.html do
-
1
render "invitations/new", layout: false
-
end
-
end
-
end
-
-
1
def create
-
13
emails = inviter_params[:emails].split(",").map(&:strip).uniq
-
-
29
valid_emails, invalid_emails = emails.partition {|email| valid_email?(email) }
-
-
13
session[:valid_email_invites] = valid_emails
-
13
session[:invalid_email_invites] = invalid_emails
-
-
13
unless valid_emails.empty?
-
7
Workers::Mail::InviteEmail.perform_async(valid_emails.join(","), current_user.id, inviter_params.to_h)
-
end
-
-
13
if emails.empty?
-
3
flash[:error] = t("invitations.create.empty")
-
10
elsif invalid_emails.empty?
-
4
flash[:notice] = t("invitations.create.sent", emails: valid_emails.join(", "))
-
6
elsif valid_emails.empty?
-
3
flash[:error] = t("invitations.create.rejected", emails: invalid_emails.join(", "))
-
else
-
3
flash[:error] = t("invitations.create.sent", emails: valid_emails.join(", ")) + ". " +
-
t("invitations.create.rejected", emails: invalid_emails.join(", "))
-
end
-
-
13
redirect_back fallback_location: stream_path
-
end
-
-
1
private
-
-
1
def check_invitations_available!
-
15
return true if AppConfig.settings.enable_registrations? || current_user.invitation_code.can_be_used?
-
-
2
flash[:error] = if AppConfig.settings.invitations.open?
-
1
t("invitations.create.no_more")
-
else
-
1
t("invitations.create.closed")
-
end
-
2
redirect_back fallback_location: stream_path
-
end
-
-
1
def valid_email?(email)
-
19
User.email_regexp.match(email).present?
-
end
-
-
1
def html_safe_string_from_session_array(key)
-
6
return "" unless session[key].present?
-
3
return "" unless session[key].respond_to?(:join)
-
2
value = session[key].join(", ").html_safe
-
2
session[key] = nil
-
2
value
-
end
-
-
1
def inviter_params
-
20
params.require(:email_inviter).permit(:message, :locale, :emails).to_h
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class LikesController < ApplicationController
-
1
include ApplicationHelper
-
1
before_action :authenticate_user!, except: :index
-
-
1
respond_to :html,
-
:mobile,
-
:json
-
-
1
rescue_from Diaspora::NonPublic do
-
1
authenticate_user!
-
end
-
-
1
def index
-
5
like = if params[:post_id]
-
5
like_service.find_for_post(params[:post_id])
-
else
-
like_service.find_for_comment(params[:comment_id])
-
end
-
3
render json: like
-
.includes(author: :profile)
-
.as_api_response(:backbone)
-
end
-
-
1
def create
-
6
like = if params[:post_id]
-
6
like_service.create_for_post(params[:post_id])
-
else
-
like_service.create_for_comment(params[:comment_id])
-
end
-
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
-
3
render plain: I18n.t("likes.create.error"), status: 422
-
else
-
2
respond_to do |format|
-
3
format.html { head :created }
-
2
format.mobile { redirect_to post_path(like.post_id) }
-
3
format.json { render json: like.as_api_response(:backbone), status: 201 }
-
end
-
end
-
-
1
def destroy
-
2
if like_service.destroy(params[:id])
-
1
head :no_content
-
else
-
1
render plain: I18n.t("likes.destroy.error"), status: 404
-
end
-
end
-
-
1
private
-
-
1
def like_service
-
13
@like_service ||= LikeService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class LinksController < ApplicationController
-
1
def resolve
-
6
entity = DiasporaLinkService.new(query).find_or_fetch_entity
-
6
raise ActiveRecord::RecordNotFound if entity.nil?
-
-
4
redirect_to url_for(entity)
-
end
-
-
1
private
-
-
1
def query
-
6
@query ||= params.fetch(:q)
-
end
-
end
-
# frozen_string_literal: true
-
-
class ManifestController < ApplicationController
-
def show # rubocop:disable Metrics/MethodLength
-
render json: {
-
short_name: AppConfig.settings.pod_name,
-
name: AppConfig.settings.pod_name,
-
description: "diaspora* is a free, decentralized and privacy-respecting social network",
-
icons: [
-
{
-
src: helpers.image_path("branding/logos/app-icon.png"),
-
type: "image/png",
-
sizes: "192x192"
-
},
-
{
-
src: helpers.image_path("branding/logos/app-icon-512.png"),
-
type: "image/png",
-
sizes: "512x512"
-
}
-
],
-
start_url: "/",
-
background_color: "#000000",
-
display: "standalone",
-
theme_color: "#000000"
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class MessagesController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
respond_to :html, :mobile
-
1
respond_to :json, :only => :show
-
-
1
def create
-
8
conversation = Conversation.find(params[:conversation_id])
-
-
8
opts = params.require(:message).permit(:text)
-
8
message = current_user.build_message(conversation, opts)
-
-
8
if message.save
-
6
logger.info "event=create type=message user=#{current_user.diaspora_handle} status=success " \
-
"message=#{message.id} chars=#{params[:message][:text].length}"
-
6
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, message)
-
else
-
2
flash[:error] = I18n.t('conversations.new_conversation.fail')
-
end
-
8
redirect_to conversations_path(:conversation_id => conversation.id)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class NodeInfoController < ApplicationController
-
1
def jrd
-
2
render json: NodeInfo.jrd(CGI.unescape(node_info_url("123.123").sub("123.123", "%{version}")))
-
end
-
-
1
def document
-
10
if NodeInfo.supported_version?(params[:version])
-
9
document = NodeInfoPresenter.new(params[:version])
-
9
render json: document, content_type: document.content_type
-
else
-
1
head :not_found
-
end
-
end
-
-
1
def statistics
-
3
respond_to do |format|
-
4
format.json { head :not_acceptable }
-
5
format.all { @statistics = NodeInfoPresenter.new("1.0") }
-
end
-
end
-
-
# TODO: this is only a dummy endpoint, because old versions of the ConnectionTester (<= 0.7.17.0)
-
# checked for this endpoint. Remove this endpoint again once most pods are updated to >= 0.7.18.0
-
1
def host_meta
-
render xml: <<~XML
-
<?xml version="1.0" encoding="UTF-8"?>
-
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
-
</XRD>
-
XML
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class NotificationsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def index
-
15
conditions = {recipient_id: current_user.id}
-
15
types = NotificationService::NOTIFICATIONS_JSON_TYPES
-
15
if params[:type] && types.has_key?(params[:type])
-
1
conditions[:type] = types[params[:type]]
-
end
-
15
if params[:show] == "unread" then conditions[:unread] = true end
-
15
page = params[:page] || 1
-
15
per_page = params[:per_page] || 25
-
15
@notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
-
15
result = Notification.where(conditions)
-
.includes(:target, actors: :profile)
-
.order("updated_at desc")
-
.limit(pager.per_page)
-
.offset(pager.offset)
-
-
15
pager.replace(result)
-
end
-
64
@group_days = @notifications.group_by {|note| note.updated_at.strftime("%Y-%m-%d") }
-
-
15
@unread_notification_count = current_user.unread_notifications.count
-
-
15
@grouped_unread_notification_counts = {}
-
-
15
types.each_with_object(current_user.unread_notifications.group_by(&:type)) {|(name, type), notifications|
-
135
@grouped_unread_notification_counts[name] = notifications.has_key?(type) ? notifications[type].count : 0
-
}
-
-
15
respond_to do |format|
-
15
format.html
-
15
format.xml { render xml: @notifications.to_xml }
-
15
format.json {
-
2
render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
-
}
-
end
-
end
-
-
1
def update
-
5
note = Notification.where(recipient_id: current_user.id, id: params[:id]).first
-
5
if note
-
4
note.set_read_state(params[:set_unread] != "true")
-
-
4
respond_to do |format|
-
8
format.json { render json: {guid: note.id, unread: note.unread} }
-
end
-
else
-
1
respond_to do |format|
-
2
format.json { render json: {}.to_json }
-
end
-
end
-
end
-
-
1
def default_serializer_options
-
{
-
12
context: self,
-
root: false
-
}
-
end
-
-
1
def read_all
-
9
current_type = NotificationService::NOTIFICATIONS_JSON_TYPES[params[:type]]
-
9
notifications = Notification.where(recipient_id: current_user.id, unread: true)
-
9
notifications = notifications.where(type: current_type) if params[:type]
-
9
notifications.update_all(unread: false)
-
9
respond_to do |format|
-
9
if current_user.unread_notifications.count > 0
-
5
format.html { redirect_to notifications_path }
-
4
format.mobile { redirect_to notifications_path }
-
else
-
10
format.html { redirect_to stream_path }
-
7
format.mobile { redirect_to stream_path }
-
end
-
9
format.xml { render xml: {}.to_xml }
-
10
format.json { render json: {}.to_json }
-
end
-
end
-
-
1
private
-
-
1
def render_as_json(unread_count, unread_count_by_type, notification_list)
-
{
-
2
unread_count: unread_count,
-
unread_count_by_type: unread_count_by_type,
-
notification_list: notification_list.map {|note|
-
4
NotificationSerializer.new(note, default_serializer_options).as_json
-
}
-
}.as_json
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ParticipationsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def create
-
4
post = current_user.find_visible_shareable_by_id(Post, params[:post_id])
-
4
if post
-
3
current_user.participate! post
-
3
head :created
-
else
-
1
head :forbidden
-
end
-
end
-
-
1
def destroy
-
2
participation = current_user.participations.find_by target_id: params[:post_id]
-
2
if participation
-
1
participation.destroy
-
1
head :ok
-
else
-
1
head :unprocessable_entity
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class PeopleController < ApplicationController
-
1
include GonHelper
-
-
1
before_action :authenticate_user!, except: %i(show stream hovercard)
-
1
before_action :find_person, only: %i(show stream hovercard)
-
1
before_action :authenticate_if_remote_profile!, only: %i(show stream)
-
-
1
respond_to :html
-
1
respond_to :json, :only => [:index, :show]
-
-
1
rescue_from ActiveRecord::RecordNotFound do
-
3
render file: Rails.root.join("public/404.html").to_s, format: :html, layout: false, status: :not_found
-
end
-
-
1
rescue_from Diaspora::AccountClosed do
-
1
respond_to do |format|
-
2
format.any { redirect_back fallback_location: root_path, notice: t("people.show.closed_account") }
-
1
format.json { head :gone }
-
end
-
end
-
-
1
helper_method :search_query
-
-
1
def index
-
21
@aspect = :search
-
21
limit = params[:limit] ? params[:limit].to_i : 15
-
-
21
@people = Person.search(search_query, current_user)
-
-
21
respond_to do |format|
-
21
format.json do
-
5
@people = @people.limit(limit)
-
5
render :json => @people
-
end
-
-
21
format.any(:html, :mobile) do
-
# only do it if it is a diaspora*-ID
-
16
if diaspora_id?(search_query)
-
6
@people = Person.where(diaspora_handle: search_query.downcase, closed_account: false)
-
6
background_search(search_query) if @people.empty?
-
end
-
16
@people = @people.paginate(:page => params[:page], :per_page => 15)
-
16
@hashes = hashes_for_people(@people, @aspects)
-
end
-
end
-
end
-
-
1
def refresh_search
-
4
@aspect = :search
-
4
@people = Person.where(diaspora_handle: search_query.downcase, closed_account: false)
-
4
@answer_html = ""
-
4
unless @people.empty?
-
1
@hashes = hashes_for_people(@people, @aspects)
-
-
1
self.formats = self.formats + [:html]
-
1
@answer_html = render_to_string :partial => 'people/person', :locals => @hashes.first
-
end
-
4
render json: {search_html: @answer_html, contacts: gon.preloads[:contacts]}.to_json
-
end
-
-
# renders the persons user profile page
-
1
def show
-
28
mark_corresponding_notifications_read if user_signed_in?
-
28
@presenter = PersonPresenter.new(@person, current_user)
-
-
28
respond_to do |format|
-
28
format.all do
-
24
if user_signed_in?
-
19
@contact = current_user.contact_for(@person)
-
end
-
24
gon.preloads[:person] = @presenter.as_json
-
24
gon.preloads[:photos_count] = Photo.visible(current_user, @person).count(:all)
-
24
respond_with @presenter, layout: "with_header"
-
end
-
-
28
format.mobile do
-
4
@post_type = :all
-
4
person_stream
-
4
respond_with @presenter
-
end
-
-
28
format.json { render json: @presenter.as_json }
-
end
-
end
-
-
1
def stream
-
10
respond_to do |format|
-
11
format.all { redirect_to person_path(@person) }
-
10
format.json {
-
24
render json: person_stream.stream_posts.map { |p| LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user)) }
-
}
-
end
-
end
-
-
# hovercards fetch some the persons public profile data via json and display
-
# it next to the avatar image in a nice box
-
1
def hovercard
-
5
respond_to do |format|
-
5
format.all do
-
1
redirect_to :action => "show", :id => params[:person_id]
-
end
-
-
5
format.json do
-
4
render json: PersonPresenter.new(@person, current_user).hovercard
-
end
-
end
-
end
-
-
1
private
-
-
1
def find_person
-
48
username = params[:username]
-
48
@person = Person.find_from_guid_or_username(
-
id: params[:id] || params[:person_id],
-
username: username
-
)
-
-
45
raise ActiveRecord::RecordNotFound if @person.nil?
-
45
raise Diaspora::AccountClosed if @person.closed_account?
-
end
-
-
1
def background_search(search_query)
-
3
Workers::FetchWebfinger.perform_async(search_query)
-
3
@background_query = search_query.downcase
-
3
gon.preloads[:background_query] = @background_query
-
end
-
-
1
def hashes_for_people(people, aspects)
-
17
people.map {|person|
-
{
-
10
person: person,
-
contact: current_user.contact_for(person) || Contact.new(person: person),
-
aspects: aspects
-
}.tap {|hash|
-
10
gon_load_contact(hash[:contact])
-
}
-
}
-
end
-
-
1
def search_query
-
109
@search_query ||= params[:q] || params[:term] || ''
-
end
-
-
1
def diaspora_id?(query)
-
24
!(query.nil? || query.lstrip.empty?) && Validation::Rule::DiasporaId.new.valid_value?(query.downcase).present?
-
end
-
-
# view this profile on the home pod, if you don't want to sign in...
-
1
def authenticate_if_remote_profile!
-
39
authenticate_user! if @person.try(:remote?)
-
end
-
-
1
def mark_corresponding_notifications_read
-
22
Notification.where(recipient_id: current_user.id, target_type: "Person", target_id: @person.id, unread: true).each do |n|
-
1
n.set_read_state( true )
-
end
-
end
-
-
1
def person_stream
-
13
@stream ||= Stream::Person.new(current_user, @person, max_time: max_time)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class PhotosController < ApplicationController
-
1
before_action :authenticate_user!, except: %i[show index]
-
1
respond_to :html, :json
-
-
1
def show
-
5
@photo = if user_signed_in?
-
4
current_user.photos_from(Person.find_by(guid: params[:person_id])).where(id: params[:id]).first
-
else
-
1
Photo.where(id: params[:id], public: true).first
-
end
-
-
5
raise ActiveRecord::RecordNotFound unless @photo
-
-
3
respond_to do |format|
-
3
format.html {
-
2
post = @photo.status_message
-
2
redirect_to post ? post_path(post) : @photo.url
-
}
-
4
format.mobile { render "photos/show" }
-
end
-
end
-
-
1
def index
-
17
@post_type = :photos
-
17
@person = Person.find_by(guid: params[:person_id])
-
17
authenticate_user! if @person.try(:remote?) && !user_signed_in?
-
16
@presenter = PersonPresenter.new(@person, current_user)
-
-
16
if @person
-
16
@contact = current_user.contact_for(@person) if user_signed_in?
-
16
@posts = Photo.visible(current_user, @person, :all, max_time)
-
16
respond_to do |format|
-
16
format.all do
-
11
gon.preloads[:person] = @presenter.as_json
-
11
gon.preloads[:photos_count] = Photo.visible(current_user, @person).count(:all)
-
11
render "people/show", layout: "with_header"
-
end
-
19
format.mobile { render "people/show" }
-
18
format.json { render_for_api :backbone, json: @posts, root: :photos }
-
end
-
else
-
flash[:error] = I18n.t "people.show.does_not_exist"
-
redirect_to people_path
-
end
-
end
-
-
1
def create
-
7
rescuing_photo_errors do
-
7
legacy_create
-
end
-
end
-
-
1
def make_profile_photo
-
3
author_id = current_user.person_id
-
3
@photo = Photo.where(id: params[:photo_id], author_id: author_id).first
-
-
3
if @photo
-
2
profile_hash = {image_url: @photo.url(:thumb_large),
-
image_url_medium: @photo.url(:thumb_medium),
-
image_url_small: @photo.url(:thumb_small)}
-
-
2
if current_user.update_profile(profile_hash)
-
2
respond_to do |format|
-
2
format.js {
-
2
render json: {photo_id: @photo.id,
-
image_url: @photo.url(:thumb_large),
-
image_url_medium: @photo.url(:thumb_medium),
-
image_url_small: @photo.url(:thumb_small),
-
author_id: author_id},
-
status: 201
-
}
-
end
-
else
-
head :unprocessable_entity
-
end
-
else
-
1
head :unprocessable_entity
-
end
-
end
-
-
1
def destroy
-
5
photo = current_user.photos.where(id: params[:id]).first
-
-
5
if photo
-
3
current_user.retract(photo)
-
-
3
respond_to do |format|
-
4
format.json { head :no_content }
-
3
format.html do
-
2
flash[:notice] = I18n.t "photos.destroy.notice"
-
2
if StatusMessage.find_by(guid: photo.status_message_guid)
-
respond_with photo, location: post_path(photo.status_message)
-
else
-
2
respond_with photo, location: person_photos_path(current_user.person)
-
end
-
end
-
end
-
else
-
2
respond_with photo, location: person_photos_path(current_user.person)
-
end
-
end
-
-
1
private
-
-
1
def photo_params
-
7
params.require(:photo).permit(:public, :text, :pending, :user_file, :image_url, :aspect_ids, :set_profile_photo)
-
end
-
-
1
def file_handler(params)
-
# For XHR file uploads, request.params[:qqfile] will be the path to the temporary file
-
# For regular form uploads (such as those made by Opera), request.params[:qqfile] will be an
-
# UploadedFile which can be returned unaltered.
-
3
if !request.params[:qqfile].is_a?(String)
-
3
qqfile = params[:qqfile]
-
# Cropped or manipulated files have their real filename only in qqfilename. Take care of this.
-
3
qqfile.original_filename = params[:qqfilename] if qqfile.original_filename == "blob"
-
3
qqfile
-
else
-
######################## dealing with local files #############
-
# get file name
-
file_name = params[:qqfile]
-
# get file content type
-
att_content_type = request.content_type.to_s == "" ? "application/octet-stream" : request.content_type.to_s
-
# create temporal file
-
file = Tempfile.new(file_name, encoding: "BINARY")
-
# put data into this file from raw post request
-
file.print request.raw_post.force_encoding("BINARY")
-
-
# create several required methods for this temporal file
-
Tempfile.send(:define_method, "content_type") { return att_content_type }
-
Tempfile.send(:define_method, "original_filename") { return file_name }
-
file
-
end
-
end
-
-
1
def legacy_create
-
7
base_params = photo_params
-
7
uploaded_file = file_handler(params)
-
-
7
@photo = photo_service.create_from_params_and_file(base_params, uploaded_file)
-
7
if @photo
-
7
respond_to do |format|
-
8
format.json { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
-
13
format.html { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
-
end
-
else
-
respond_with @photo, location: photos_path, error: message
-
end
-
end
-
-
1
def rescuing_photo_errors
-
7
yield
-
rescue TypeError
-
return_photo_error I18n.t "photos.create.type_error"
-
rescue CarrierWave::IntegrityError
-
return_photo_error I18n.t "photos.create.integrity_error"
-
rescue RuntimeError
-
return_photo_error I18n.t "photos.create.runtime_error"
-
end
-
-
1
def return_photo_error(message)
-
respond_to do |format|
-
format.json { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
-
format.html { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
-
end
-
end
-
-
1
def photo_service
-
7
@photo_service ||= PhotoService.new(current_user, false)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PollParticipationsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def create
-
5
poll_participation = poll_service.vote(params[:post_id], params[:poll_answer_id])
-
2
respond_to do |format|
-
2
format.mobile { redirect_to stream_path }
-
4
format.json { render json: poll_participation, :status => 201 }
-
end
-
rescue ActiveRecord::RecordInvalid
-
1
respond_to do |format|
-
1
format.mobile { redirect_to stream_path }
-
2
format.json { head :forbidden }
-
end
-
end
-
-
1
private
-
-
1
def poll_service
-
5
@poll_service ||= PollParticipationService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class PostsController < ApplicationController
-
1
before_action :authenticate_user!, only: %i(destroy mentionable)
-
1
before_action :set_format_if_malformed_from_status_net, only: :show
-
-
1
respond_to :html, :mobile, :json
-
-
1
rescue_from Diaspora::NonPublic do
-
1
authenticate_user!
-
end
-
-
1
rescue_from Diaspora::NotMine do
-
1
render plain: I18n.t("posts.show.forbidden"), status: 403
-
end
-
-
1
def show
-
14
post = post_service.find!(params[:id])
-
12
post_service.mark_user_notifications(post.id)
-
12
presenter = PostPresenter.new(post, current_user)
-
12
respond_to do |format|
-
12
format.html do
-
6
gon.post = presenter.with_initial_interactions
-
6
render locals: {post: presenter}
-
end
-
17
format.mobile { render locals: {post: post} }
-
13
format.json { render json: presenter.with_interactions }
-
end
-
end
-
-
1
def oembed
-
2
post_id = OEmbedPresenter.id_from_url(params.delete(:url))
-
2
post = post_service.find!(post_id)
-
1
oembed = params.slice(:format, :maxheight, :minheight)
-
1
render json: OEmbedPresenter.new(post, oembed)
-
rescue
-
1
head :not_found
-
end
-
-
1
def mentionable
-
4
respond_to do |format|
-
4
format.json {
-
3
if params[:id].present? && params[:q].present?
-
2
render json: post_service.mentionable_in_comment(params[:id], params[:q])
-
else
-
1
head :no_content
-
end
-
}
-
5
format.any { head :not_acceptable }
-
end
-
rescue ActiveRecord::RecordNotFound
-
1
head :not_found
-
end
-
-
1
def destroy
-
4
post_service.destroy(params[:id])
-
2
respond_to do |format|
-
3
format.json { head :no_content }
-
3
format.any { redirect_to stream_path }
-
end
-
end
-
-
1
private
-
-
1
def post_service
-
37
@post_service ||= PostService.new(current_user)
-
end
-
-
1
def set_format_if_malformed_from_status_net
-
14
request.format = :html if request.format == "application/html+xml"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ProfilesController < ApplicationController
-
1
before_action :authenticate_user!, :except => ['show']
-
-
1
respond_to :html, :except => [:show]
-
1
respond_to :js, :only => :update
-
-
# this is terrible because we're actually serving up the associated person here;
-
# however, this is the effect that we want for now
-
1
def show
-
1
@person = Person.find_by_guid!(params[:id])
-
-
1
respond_to do |format|
-
2
format.json { render :json => PersonPresenter.new(@person, current_user) }
-
end
-
end
-
-
1
def edit
-
4
@person = current_user.person
-
4
@aspect = :person_edit
-
4
@profile = @person.profile
-
-
12
gon.preloads[:tagsArray] = @profile.tags.map {|tag| {name: "##{tag.name}", value: "##{tag.name}"} }
-
end
-
-
1
def update
-
# upload and set new profile photo
-
11
@profile_attrs = profile_params
-
-
11
munge_tag_string
-
-
#checkbox tags wtf
-
11
@profile_attrs[:searchable] ||= false
-
11
@profile_attrs[:nsfw] ||= false
-
11
@profile_attrs[:public_details] ||= false
-
-
11
if params[:photo_id]
-
@profile_attrs[:photo] = Photo.where(:author_id => current_user.person_id, :id => params[:photo_id]).first
-
end
-
-
11
if current_user.update_profile(@profile_attrs)
-
10
flash[:notice] = I18n.t 'profiles.update.updated'
-
else
-
1
flash[:error] = I18n.t 'profiles.update.failed'
-
end
-
-
11
respond_to do |format|
-
11
format.js { head :ok }
-
11
format.any {
-
11
if current_user.getting_started?
-
redirect_to getting_started_path
-
else
-
11
redirect_to edit_profile_path
-
end
-
}
-
end
-
end
-
-
1
private
-
-
1
def munge_tag_string
-
11
unless @profile_attrs[:tag_string].nil? || @profile_attrs[:tag_string] == I18n.t('profiles.edit.your_tags_placeholder')
-
3
@profile_attrs[:tag_string].split( " " ).each do |extra_tag|
-
2
extra_tag.strip!
-
2
unless extra_tag == ""
-
2
extra_tag = "##{extra_tag}" unless extra_tag.start_with?( "#" )
-
2
params[:tags] += " #{extra_tag}"
-
end
-
end
-
end
-
11
@profile_attrs[:tag_string] = (params[:tags]) ? params[:tags].gsub(',',' ') : ""
-
end
-
-
1
def profile_params
-
11
params.require(:profile).permit(:first_name, :last_name, :gender, :bio,
-
:location, :searchable, :tag_string, :nsfw,
-
:public_details, date: %i[year month day]).to_h || {}
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class RegistrationsController < Devise::RegistrationsController
-
1
before_action :check_registrations_open_or_valid_invite!, except: :registrations_closed
-
-
10
layout -> { request.format == :mobile ? "application" : "with_header_with_footer" }
-
-
1
def create
-
14
@user = User.build(user_params)
-
-
14
if @user.sign_up
-
7
flash[:notice] = t("registrations.create.success")
-
7
@user.process_invite_acceptence(invite) if invite.present?
-
7
@user.seed_aspects
-
7
@user.send_welcome_message
-
7
sign_in_and_redirect(:user, @user)
-
7
logger.info "event=registration status=successful user=#{@user.diaspora_handle}"
-
else
-
7
@user.errors.delete(:person)
-
-
7
flash.now[:error] = @user.errors.full_messages.join(" - ")
-
7
logger.info "event=registration status=failure errors='#{@user.errors.full_messages.join(', ')}'"
-
7
render action: "new"
-
end
-
end
-
-
1
def registrations_closed
-
render "registrations/registrations_closed"
-
end
-
-
1
private
-
-
1
def check_registrations_open_or_valid_invite!
-
21
return true if AppConfig.settings.enable_registrations? || invite.try(:can_be_used?)
-
-
5
flash[:error] = t("registrations.invalid_invite") if params[:invite]
-
5
redirect_to registrations_closed_path
-
end
-
-
1
def invite
-
27
@invite ||= InvitationCode.find_by_token(params[:invite][:token]) if params[:invite].present?
-
end
-
-
1
helper_method :invite
-
-
1
def user_params
-
14
params.require(:user).permit(
-
:username, :email, :getting_started, :password, :password_confirmation, :language, :disable_mail,
-
:show_community_spotlight_in_stream, :auto_follow_back, :auto_follow_back_aspect_id,
-
:remember_me, :captcha, :captcha_key
-
)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ReportController < ApplicationController
-
1
before_action :authenticate_user!
-
1
before_action :redirect_unless_moderator, except: [:create]
-
-
1
def index
-
2
@reports = Report.where(reviewed: false)
-
end
-
-
1
def update
-
4
if report = Report.where(id: params[:id]).first
-
report.mark_as_reviewed
-
end
-
4
redirect_to :action => :index
-
end
-
-
1
def destroy
-
4
if (report = Report.where(id: params[:id]).first) && report.destroy_reported_item
-
flash[:notice] = I18n.t "report.status.destroyed"
-
else
-
4
flash[:error] = I18n.t "report.status.failed"
-
end
-
4
redirect_to action: :index
-
end
-
-
1
def create
-
2
report = current_user.reports.new(report_params)
-
2
if report.save
-
2
render json: true, status: 200
-
else
-
head :conflict
-
end
-
end
-
-
1
private
-
1
def report_params
-
2
params.require(:report).permit(:item_id, :item_type, :text)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ResharesController < ApplicationController
-
1
before_action :authenticate_user!, except: :index
-
1
respond_to :json
-
-
1
def create
-
5
reshare = reshare_service.create(params[:root_guid])
-
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
-
1
render plain: I18n.t("reshares.create.error"), status: 422
-
else
-
4
render json: PostPresenter.new(reshare, current_user).with_interactions, status: 201
-
end
-
-
1
def index
-
5
render json: reshare_service.find_for_post(params[:post_id])
-
.includes(author: :profile)
-
.as_api_response(:backbone)
-
end
-
-
1
private
-
-
1
def reshare_service
-
10
@reshare_service ||= ReshareService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class SearchController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def search
-
4
if search_query.starts_with?('#')
-
3
if search_query.length > 1
-
2
respond_to do |format|
-
2
format.json {redirect_to tags_path(:q => search_query.delete("#."))}
-
4
format.any {redirect_to tag_path(:name => search_query.delete("#."))}
-
end
-
else
-
1
flash[:error] = I18n.t('tags.show.none', :name => search_query)
-
1
redirect_back fallback_location: stream_path
-
end
-
else
-
1
redirect_to people_path(:q => search_query)
-
end
-
end
-
-
1
private
-
-
1
def search_query
-
13
@search_query ||= (params[:q] || params[:term] || '').strip
-
end
-
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
1
class ServicesController < ApplicationController
-
# We need to take a raw POST from an omniauth provider with no authenticity token.
-
# See https://github.com/intridea/omniauth/issues/203
-
# See also http://www.communityguides.eu/articles/16
-
1
skip_before_action :verify_authenticity_token, :only => :create
-
1
before_action :authenticate_user!
-
1
before_action :abort_if_already_authorized, :abort_if_read_only_access, :only => :create
-
-
1
respond_to :html
-
1
respond_to :json, :only => :inviter
-
-
1
def index
-
1
@services = current_user.services
-
end
-
-
1
def create
-
4
service = Service.initialize_from_omniauth( omniauth_hash )
-
-
4
if current_user.services << service
-
4
no_profile_image_before_update = no_profile_image?
-
4
current_user.update_profile_with_omniauth(service.info)
-
4
fetch_photo(service) if no_profile_image_before_update
-
-
4
flash[:notice] = I18n.t 'services.create.success'
-
else
-
flash[:error] = I18n.t 'services.create.failure'
-
end
-
4
redirect_to_origin
-
end
-
-
1
def failure
-
logger.info "error in oauth #{params.inspect}"
-
flash[:error] = t('services.failure.error')
-
redirect_to services_url
-
end
-
-
1
def destroy
-
1
@service = current_user.services.find(params[:id])
-
1
@service.destroy
-
1
flash[:notice] = I18n.t 'services.destroy.success'
-
1
redirect_to services_url
-
end
-
-
1
private
-
-
1
def abort_if_already_authorized
-
8
if service = Service.where(uid: omniauth_hash['uid']).first
-
2
flash[:error] = I18n.t( 'services.create.already_authorized',
-
diaspora_id: service.user.profile.diaspora_handle,
-
service_name: service.provider.camelize )
-
2
redirect_to_origin
-
end
-
end
-
-
1
def abort_if_read_only_access
-
6
if omniauth_hash['provider'] == 'twitter' && twitter_access_level == 'read'
-
2
flash[:error] = I18n.t( 'services.create.read_only_access' )
-
2
redirect_to_origin
-
end
-
end
-
-
1
def redirect_to_origin
-
8
if origin
-
8
redirect_to origin
-
else
-
render(text: "<script>window.close()</script>")
-
end
-
end
-
-
1
def no_profile_image?
-
2
current_user.profile[:image_url].blank?
-
end
-
-
1
def fetch_photo(service)
-
3
Workers::FetchProfilePhoto.perform_async(current_user.id, service.id, service.info["image"])
-
end
-
-
1
def origin
-
16
request.env['omniauth.origin']
-
end
-
-
1
def omniauth_hash
-
20
request.env['omniauth.auth']
-
end
-
-
1
def twitter_access_token
-
2
omniauth_hash['extra']['access_token']
-
end
-
-
#https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema #=> normalized hash
-
#https://gist.github.com/oliverbarnes/6096959 #=> hash with twitter specific extra
-
1
def twitter_access_level
-
2
twitter_access_token.response.header['x-access-level']
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class SessionsController < Devise::SessionsController
-
# rubocop:disable Rails/LexicallyScopedActionFilter
-
1
before_action :authenticate_with_2fa, only: :create
-
# rubocop:enable Rails/LexicallyScopedActionFilter
-
-
1
def find_user
-
3
return User.find_for_authentication(username: params[:user][:username]) if params[:user][:username]
-
-
User.find(session[:otp_user_id]) if session[:otp_user_id]
-
end
-
-
1
def authenticate_with_2fa
-
3
self.resource = find_user
-
-
3
return true unless resource&.otp_required_for_login?
-
-
if params[:user][:otp_attempt].present? && session[:otp_user_id]
-
authenticate_with_two_factor_via_otp(resource)
-
else
-
strategy = Warden::Strategies[:database_authenticatable].new(warden.env, :user)
-
prompt_for_two_factor(strategy.user) if strategy.valid? && strategy._run!.successful?
-
end
-
end
-
-
1
def valid_otp_attempt?(user)
-
user.validate_and_consume_otp!(params[:user][:otp_attempt]) ||
-
user.invalidate_otp_backup_code!(params[:user][:otp_attempt])
-
rescue OpenSSL::Cipher::CipherError => _error
-
false
-
end
-
-
1
def authenticate_with_two_factor_via_otp(user)
-
if valid_otp_attempt?(user)
-
session.delete(:otp_user_id)
-
sign_in(user)
-
else
-
flash.now[:alert] = "Invalid token"
-
prompt_for_two_factor(user)
-
end
-
end
-
-
1
def prompt_for_two_factor(user)
-
session[:otp_user_id] = user.id
-
render :two_factor
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
class ShareVisibilitiesController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
def update
-
4
post = post_service.find!(params[:post_id])
-
2
current_user.toggle_hidden_shareable(post)
-
2
head :ok
-
end
-
-
1
private
-
-
1
def post_service
-
4
@post_service ||= PostService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
1
class StatusMessagesController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
before_action :remove_getting_started, only: :create
-
-
1
respond_to :html, :mobile, :json
-
-
1
layout "application", only: :bookmarklet
-
-
# Called when a user clicks "Mention" on a profile page
-
# @param person_id [Integer] The id of the person to be mentioned
-
1
def new
-
3
if params[:person_id] && fetch_person(params[:person_id])
-
2
@aspect = :profile
-
2
@contact = current_user.contact_for(@person)
-
2
if @contact
-
2
@aspects_with_person = @contact.aspects.load
-
2
render layout: nil
-
else
-
@aspects_with_person = []
-
end
-
1
elsif request.format == :mobile
-
@aspect = :all
-
@aspects = current_user.aspects.load
-
else
-
1
redirect_to stream_path
-
end
-
end
-
-
1
def bookmarklet
-
4
@aspects = current_user.aspects
-
-
4
gon.preloads[:bookmarklet] = {
-
content: params[:content],
-
title: params[:title],
-
url: params[:url],
-
notes: params[:notes]
-
}
-
end
-
-
1
def create
-
25
status_message = StatusMessageCreationService.new(current_user).create(normalize_params)
-
22
respond_to do |format|
-
23
format.mobile { redirect_to stream_path }
-
35
format.json { render json: PostPresenter.new(status_message, current_user), status: 201 }
-
end
-
rescue StatusMessageCreationService::BadAspectsIDs
-
1
render status: 422, plain: I18n.t("status_messages.bad_aspects")
-
rescue StandardError => error
-
2
handle_create_error(error)
-
end
-
-
1
private
-
-
1
def fetch_person(person_id)
-
2
@person = Person.where(id: person_id).first
-
end
-
-
1
def handle_create_error(error)
-
2
logger.debug error
-
2
respond_to do |format|
-
3
format.mobile { redirect_to stream_path }
-
3
format.json { render plain: error.message, status: 403 }
-
end
-
end
-
-
1
def comes_from_others_profile_page?
-
coming_from_profile_page? && !own_profile_page?
-
end
-
-
1
def coming_from_profile_page?
-
request.env["HTTP_REFERER"].include?("people")
-
end
-
-
1
def own_profile_page?
-
request.env["HTTP_REFERER"].include?("/people/" + current_user.guid)
-
end
-
-
1
def normalize_params
-
25
params.permit(
-
:location_address,
-
:location_coords,
-
:poll_question,
-
status_message: %i[text provider_display_name],
-
poll_answers: []
-
).to_h.merge(
-
services: [*params[:services]].compact,
-
aspect_ids: normalize_aspect_ids,
-
public: [*params[:aspect_ids]].first == "public",
-
photos: [*params[:photos]].compact
-
)
-
end
-
-
1
def normalize_aspect_ids
-
25
aspect_ids = [*params[:aspect_ids]]
-
25
if aspect_ids.first == "all_aspects"
-
2
current_user.aspect_ids
-
else
-
23
aspect_ids
-
end
-
end
-
-
1
def remove_getting_started
-
26
current_user.disable_getting_started
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class StreamsController < ApplicationController
-
1
before_action :authenticate_user!, except: :public
-
1
before_action :save_selected_aspects, :only => :aspects
-
-
23
layout proc { request.format == :mobile ? "application" : "with_header" }
-
-
1
respond_to :html,
-
:mobile,
-
:json
-
-
1
def aspects
-
12
aspect_ids = (session[:a_ids] || [])
-
12
@stream = Stream::Aspect.new(current_user, aspect_ids,
-
:max_time => max_time)
-
12
stream_responder
-
end
-
-
1
def public
-
3
stream_responder(Stream::Public)
-
end
-
-
1
def local_public
-
if AppConfig.local_posts_stream?(current_user)
-
stream_responder(Stream::LocalPublic)
-
else
-
head :not_found
-
end
-
end
-
-
1
def activity
-
1
stream_responder(Stream::Activity)
-
end
-
-
1
def multi
-
4
if current_user.getting_started
-
1
gon.preloads[:getting_started] = true
-
1
inviter = current_user.invited_by.try(:person)
-
1
gon.preloads[:mentioned_person] = {name: inviter.name, handle: inviter.diaspora_handle} if inviter
-
end
-
-
4
stream_responder(Stream::Multi)
-
end
-
-
1
def commented
-
stream_responder(Stream::Comments)
-
end
-
-
1
def liked
-
1
stream_responder(Stream::Likes)
-
end
-
-
1
def mentioned
-
1
stream_responder(Stream::Mention)
-
end
-
-
1
def followed_tags
-
1
gon.preloads[:tagFollowings] = tags
-
1
stream_responder(Stream::FollowedTag)
-
end
-
-
1
private
-
-
1
def stream_responder(stream_klass=nil)
-
-
23
if stream_klass.present?
-
11
@stream ||= stream_klass.new(current_user, :max_time => max_time)
-
end
-
-
23
respond_with do |format|
-
40
format.html { render 'streams/main_stream' }
-
28
format.mobile { render 'streams/main_stream' }
-
39
format.json { render :json => @stream.stream_posts.map {|p| LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user)) }}
-
end
-
end
-
-
1
def save_selected_aspects
-
12
if params[:a_ids].present?
-
session[:a_ids] = params[:a_ids]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
class TagFollowingsController < ApplicationController
-
1
before_action :authenticate_user!
-
-
1
respond_to :json
-
1
respond_to :html, only: [:manage]
-
-
# POST /tag_followings
-
# POST /tag_followings.xml
-
1
def create
-
9
tag = tag_followings_service.create(params["name"])
-
6
render json: tag.to_json, status: :created
-
rescue TagFollowingService::DuplicateTag
-
render json: tag_followings_service.find(params["name"]), status: :created
-
rescue StandardError
-
3
head :forbidden
-
end
-
-
# DELETE /tag_followings/1
-
# DELETE /tag_followings/1.xml
-
1
def destroy
-
1
tag_followings_service.destroy(params["id"])
-
-
1
respond_to do |format|
-
2
format.any(:js, :json) { head :no_content }
-
end
-
rescue ActiveRecord::RecordNotFound
-
respond_to do |format|
-
format.any(:js, :json) { head :forbidden }
-
end
-
end
-
-
1
def index
-
1
respond_to do |format|
-
2
format.json{ render(:json => tags.to_json, :status => 200) }
-
end
-
end
-
-
1
def manage
-
2
redirect_to followed_tags_stream_path unless request.format == :mobile
-
2
gon.preloads[:tagFollowings] = tags
-
end
-
-
1
private
-
-
1
def tag_followings_service
-
10
TagFollowingService.new(current_user)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class TagsController < ApplicationController
-
1
before_action :ensure_page, :only => :show
-
-
1
helper_method :tag_followed?
-
-
11
layout proc { request.format == :mobile ? "application" : "with_header" }, only: :show
-
-
1
respond_to :html, :only => [:show]
-
1
respond_to :json, :only => [:index, :show]
-
-
1
def index
-
4
if params[:q] && params[:q].length > 1
-
1
params[:q].gsub!("#", "")
-
1
params[:limit] = !params[:limit].blank? ? params[:limit].to_i : 10
-
1
@tags = ActsAsTaggableOn::Tag.autocomplete(params[:q]).limit(params[:limit] - 1)
-
1
prep_tags_for_javascript
-
-
1
respond_to do |format|
-
2
format.json{ render(:json => @tags.to_json, :status => 200) }
-
end
-
else
-
3
respond_to do |format|
-
5
format.json { head :unprocessable_entity }
-
4
format.html { redirect_to tag_path("partytimeexcellent") }
-
end
-
end
-
end
-
-
1
def show
-
13
redirect_to(:action => :show, :name => downcased_tag_name) && return if tag_has_capitals?
-
-
12
if user_signed_in?
-
4
gon.preloads[:tagFollowings] = tags
-
end
-
12
stream = Stream::Tag.new(current_user, params[:name], max_time: max_time, page: params[:page])
-
12
@stream = TagStreamPresenter.new(stream)
-
12
respond_with do |format|
-
12
format.json do
-
2
posts = stream.stream_posts.map do |p|
-
2
LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user))
-
end
-
2
render json: posts
-
end
-
end
-
end
-
-
1
private
-
-
1
def tag_followed?
-
2
TagFollowing.user_is_following?(current_user, params[:name])
-
end
-
-
1
def tag_has_capitals?
-
13
mb_tag = params[:name].mb_chars
-
13
mb_tag.downcase != mb_tag
-
end
-
-
1
def downcased_tag_name
-
1
params[:name].mb_chars.downcase.to_s
-
end
-
-
1
def prep_tags_for_javascript
-
1
@tags = @tags.map {|tag|
-
1
{ :name => ("#" + tag.name) }
-
}
-
-
1
@tags << { :name => ('#' + params[:q]) }
-
1
@tags.uniq!
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class TermsController < ApplicationController
-
-
1
respond_to :html, :mobile
-
-
1
def index
-
2
partial_dir = Rails.root.join('app', 'views', 'terms')
-
2
if partial_dir.join('terms.haml').exist? ||
-
partial_dir.join('terms.erb').exist?
-
render :terms
-
else
-
2
render :default
-
end
-
end
-
-
end
-
# frozen_string_literal: true
-
-
1
class TwoFactorAuthenticationsController < ApplicationController
-
1
before_action :authenticate_user!
-
1
before_action :verify_otp_required, only: [:create]
-
-
1
def show
-
2
@user = current_user
-
end
-
-
1
def create
-
1
current_user.otp_secret = User.generate_otp_secret(32)
-
1
current_user.save!
-
1
redirect_to confirm_two_factor_authentication_path
-
end
-
-
1
def confirm_2fa
-
2
redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
-
end
-
-
1
def confirm_and_activate_2fa
-
2
if current_user.validate_and_consume_otp!(params[:user][:code])
-
1
current_user.otp_required_for_login = true
-
1
current_user.save!
-
-
1
flash[:notice] = t("two_factor_auth.flash.success_activation")
-
1
redirect_to recovery_codes_two_factor_authentication_path
-
else
-
1
flash[:alert] = t("two_factor_auth.flash.error_token")
-
1
redirect_to confirm_two_factor_authentication_path
-
end
-
end
-
-
1
def recovery_codes
-
1
@recovery_codes = current_user.generate_otp_backup_codes!
-
1
current_user.save!
-
end
-
-
1
def destroy
-
2
if current_user.valid_password?(params[:two_factor_authentication][:password])
-
1
current_user.otp_required_for_login = false
-
1
current_user.save!
-
1
flash[:notice] = t("two_factor_auth.flash.success_deactivation")
-
else
-
1
flash[:alert] = t("users.destroy.wrong_password")
-
end
-
2
redirect_to two_factor_authentication_path
-
end
-
-
1
private
-
-
1
def verify_otp_required
-
1
redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class UsersController < ApplicationController
-
1
before_action :authenticate_user!, except: %i(new create public)
-
1
respond_to :html
-
-
1
def edit
-
4
@user = current_user
-
4
set_email_preferences
-
end
-
-
1
def privacy_settings
-
1
@blocks = current_user.blocks.includes(:person)
-
end
-
-
1
def update
-
38
@user = current_user
-
-
38
if params[:change_password] && user_password_params
-
1
password_changed = change_password(user_password_params)
-
1
return redirect_to new_user_session_path if password_changed
-
37
elsif user_params
-
37
update_user(user_params)
-
end
-
-
38
set_email_preferences
-
38
render :edit
-
end
-
-
1
def destroy
-
3
if params[:user] && params[:user][:current_password] && current_user.valid_password?(params[:user][:current_password])
-
2
current_user.close_account!
-
2
sign_out current_user
-
2
redirect_to(new_user_session_path(format: request[:format]), notice: I18n.t("users.destroy.success"))
-
else
-
1
if params[:user].present? && params[:user][:current_password].present?
-
1
flash[:error] = t "users.destroy.wrong_password"
-
else
-
flash[:error] = t "users.destroy.no_password"
-
end
-
1
redirect_back fallback_location: edit_user_path
-
end
-
end
-
-
1
def public
-
8
if @user = User.find_by_username(params[:username])
-
8
respond_to do |format|
-
8
format.atom do
-
6
@posts = Post.where(author_id: @user.person_id, public: true)
-
.order("created_at DESC")
-
.limit(25)
-
6
.map {|post| post.is_a?(Reshare) ? post.absolute_root : post }
-
.compact
-
end
-
-
10
format.any { redirect_to person_path(@user.person) }
-
end
-
else
-
redirect_to stream_path, error: I18n.t("users.public.does_not_exist", username: params[:username])
-
end
-
end
-
-
1
def getting_started
-
5
@user = current_user
-
5
@person = @user.person
-
5
@profile = @user.profile
-
5
gon.preloads[:inviter] = PersonPresenter.new(current_user.invited_by.try(:person), current_user).as_json
-
5
gon.preloads[:tagsArray] = current_user.followed_tags.map {|tag| {name: "##{tag.name}", value: "##{tag.name}"} }
-
-
5
render "users/getting_started"
-
end
-
-
1
def getting_started_completed
-
user = current_user
-
user.getting_started = false
-
user.save
-
redirect_to stream_path
-
end
-
-
1
def export_profile
-
1
current_user.queue_export
-
1
flash[:notice] = I18n.t("users.edit.export_in_progress")
-
1
redirect_to edit_user_path
-
end
-
-
1
def download_profile
-
1
redirect_to current_user.export.url
-
end
-
-
1
def export_photos
-
1
current_user.queue_export_photos
-
1
flash[:notice] = I18n.t("users.edit.export_photos_in_progress")
-
1
redirect_to edit_user_path
-
end
-
-
1
def download_photos
-
1
redirect_to current_user.exported_photos_file.url
-
end
-
-
1
def confirm_email
-
3
if current_user.confirm_email(params[:token])
-
2
flash[:notice] = I18n.t("users.confirm_email.email_confirmed", email: current_user.email)
-
1
elsif current_user.unconfirmed_email.present?
-
1
flash[:error] = I18n.t("users.confirm_email.email_not_confirmed")
-
end
-
3
redirect_to edit_user_path
-
end
-
-
1
private
-
-
1
def user_params
-
74
params.fetch(:user).permit(
-
:email,
-
:language,
-
:color_theme,
-
:disable_mail,
-
:show_community_spotlight_in_stream,
-
:auto_follow_back,
-
:auto_follow_back_aspect_id,
-
:getting_started,
-
:post_default_public,
-
:exported_photos_file,
-
:export,
-
email_preferences: UserPreference::VALID_EMAIL_TYPES.map(&:to_sym)
-
)
-
end
-
-
1
def user_password_params
-
2
params.fetch(:user).permit(
-
:current_password,
-
:password,
-
:password_confirmation
-
)
-
end
-
-
1
def update_user(user_data)
-
37
if user_data[:email_preferences]
-
22
change_email_preferences(user_data)
-
15
elsif user_data[:language]
-
3
change_language(user_data)
-
12
elsif user_data[:email]
-
7
change_email(user_data)
-
5
elsif user_data[:auto_follow_back]
-
change_settings(user_data, "users.update.follow_settings_changed", "users.update.follow_settings_not_changed")
-
5
elsif user_data[:post_default_public]
-
change_post_default(user_data)
-
5
elsif user_data[:color_theme]
-
1
change_settings(user_data, "users.update.color_theme_changed", "users.update.color_theme_not_changed")
-
4
elsif user_data[:export] || user_data[:exported_photos_file]
-
upload_export_files(user_data)
-
else
-
4
change_settings(user_data)
-
end
-
end
-
-
1
def change_password(password_params)
-
1
if @user.update_with_password(password_params)
-
flash[:notice] = t("users.update.password_changed")
-
true
-
else
-
1
flash.now[:error] = t("users.update.password_not_changed")
-
1
false
-
end
-
end
-
-
1
def change_post_default(user_data)
-
# by default user_data[:post_default_public] is set to false
-
case params[:aspect_ids].try(:first)
-
when "public"
-
user_data[:post_default_public] = true
-
when "all_aspects"
-
params[:aspect_ids] = @user.aspects.map {|a| a.id.to_s }
-
end
-
@user.update_post_default_aspects params[:aspect_ids].to_a
-
change_settings(user_data)
-
end
-
-
# change email notifications
-
1
def change_email_preferences(user_data)
-
22
@user.update_user_preferences(user_data[:email_preferences])
-
22
flash.now[:notice] = t("users.update.email_notifications_changed")
-
end
-
-
1
def change_language(user_data)
-
3
if @user.update(user_data)
-
2
I18n.locale = @user.language
-
2
flash.now[:notice] = t("users.update.language_changed")
-
else
-
1
flash.now[:error] = t("users.update.language_not_changed")
-
end
-
end
-
-
1
def change_email(user_data)
-
7
if AppConfig.mail.enable?
-
6
@user.unconfirmed_email = user_data[:email]
-
6
if @user.save
-
5
@user.send_confirm_email
-
5
flash.now[:notice] = t("users.update.unconfirmed_email_changed")
-
else
-
1
@user.reload # match user object with the database
-
1
flash.now[:error] = t("users.update.unconfirmed_email_not_changed")
-
end
-
else
-
1
@user.email = user_data[:email]
-
1
if @user.save
-
1
flash.now[:notice] = t("users.update.settings_updated")
-
else
-
@user.reload
-
flash.now[:error] = t("users.update.unconfirmed_email_not_changed")
-
end
-
end
-
end
-
-
1
def upload_export_files(user_data)
-
logger.info "Start importing account"
-
@user.export = user_data[:export] if user_data[:export]
-
@user.exported_photos_file = user_data[:exported_photos_file] if user_data[:exported_photos_file]
-
if @user.save
-
flash.now[:notice] = "Your account migration has been scheduled"
-
else
-
flash.now[:error] = "Your account migration could not be scheduled for the following reason:"\
-
" #{@user.errors.full_messages}"
-
end
-
Workers::ImportUser.perform_async(@user.id)
-
end
-
-
1
def change_settings(user_data, successful="users.update.settings_updated", error="users.update.settings_not_updated")
-
5
if @user.update(user_data)
-
4
flash.now[:notice] = t(successful)
-
else
-
1
flash.now[:error] = t(error)
-
end
-
end
-
-
1
def set_email_preferences
-
42
@email_prefs = Hash.new(true)
-
-
42
@user.user_preferences.each do |pref|
-
23
@email_prefs[pref.email_type] = false
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActivityStreamsHelper
-
def add_activitystreams_author(target, person)
-
target.author do |author|
-
author.name person.name
-
author.uri local_or_remote_person_path(person, absolute: true)
-
-
author.tag! "activity:object-type", "http://activitystrea.ms/schema/1.0/person"
-
author.tag! "poco:preferredUsername", person.username
-
author.tag! "poco:displayName", person.name
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module ApplicationHelper
-
def pod_name
-
AppConfig.settings.pod_name
-
end
-
-
def pod_version
-
AppConfig.version.number
-
end
-
-
def uri_with_username
-
if user_signed_in?
-
AppConfig.pod_uri + "?username=#{current_user.username}"
-
else
-
AppConfig.pod_uri
-
end
-
end
-
-
def changelog_url
-
return AppConfig.settings.changelog_url.get if AppConfig.settings.changelog_url.present?
-
-
url = "https://github.com/diaspora/diaspora/blob/master/Changelog.md"
-
return url if AppConfig.git_revision.blank?
-
-
url.sub("/master/", "/#{AppConfig.git_revision}/")
-
end
-
-
def source_url
-
AppConfig.settings.source_url.presence || "#{root_path.chomp('/')}/source.tar.gz"
-
end
-
-
def donations_enabled?
-
AppConfig.settings.paypal_donations.enable? ||
-
AppConfig.settings.liberapay_username.present? ||
-
AppConfig.bitcoin_donation_address.present?
-
end
-
-
def timeago(time, options={})
-
timeago_tag(time, options.merge(:class => 'timeago', :title => time.iso8601, :force => true)) if time
-
end
-
-
def bookmarklet_code(height=400, width=620)
-
"javascript:" +
-
BookmarkletRenderer.body +
-
"bookmarklet('#{bookmarklet_url}', #{width}, #{height});"
-
end
-
-
def all_services_connected?
-
current_user.services.size == AppConfig.configured_services.size
-
end
-
-
def service_unconnected?(service)
-
AppConfig.show_service?(service, current_user) && current_user.services.none? {|x| x.provider == service }
-
end
-
-
def popover_with_close_html(without_close_html)
-
without_close_html + link_to('×'.html_safe, "#", :class => 'close')
-
end
-
-
# Require jQuery from CDN if possible, falling back to vendored copy, and require
-
# vendored jquery_ujs
-
def jquery_include_tag
-
buf = []
-
if AppConfig.privacy.jquery_cdn?
-
version = Jquery::Rails::JQUERY_3_VERSION
-
buf << [javascript_include_tag("//code.jquery.com/jquery-#{version}.min.js")]
-
buf << [
-
nonced_javascript_tag("!window.jQuery && document.write(unescape('#{j javascript_include_tag('jquery3')}'));")
-
]
-
else
-
buf << [javascript_include_tag("jquery3")]
-
end
-
buf << [javascript_include_tag("jquery_ujs")]
-
buf << [nonced_javascript_tag("jQuery.ajaxSetup({'cache': false});")]
-
buf << [nonced_javascript_tag("$.fx.off = true;")] if Rails.env.test?
-
buf.join("\n").html_safe
-
end
-
-
def qrcode_uri
-
label = current_user.username
-
current_user.otp_provisioning_uri(label, issuer: AppConfig.environment.url)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module AspectGlobalHelper
-
def aspect_options_for_select(aspects)
-
options = {}
-
aspects.each do |aspect|
-
options[aspect.to_s] = aspect.id
-
end
-
options
-
end
-
-
def publisher_aspects_for(stream)
-
if stream
-
aspects = stream.aspects
-
aspect = stream.aspect
-
elsif current_user
-
aspects = current_user.post_default_aspects
-
aspect = aspects.first
-
else
-
return {}
-
end
-
{selected_aspects: aspects, aspect: aspect}
-
end
-
end
-
# frozen_string_literal: true
-
-
module ContactsHelper
-
def start_a_conversation_link(aspect, contacts_size)
-
conv_opts = { class: "conversation_button contacts_button"}
-
-
content_tag :span, conv_opts do
-
content_tag :i,
-
nil,
-
class: "entypo-mail contacts-header-icon",
-
title: t("contacts.index.start_a_conversation")
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ConversationsHelper
-
def conversation_class(conversation, unread_count, selected_conversation_id)
-
conv_class = unread_count > 0 ? "unread" : ""
-
return conv_class unless selected_conversation_id && conversation.id == selected_conversation_id
-
-
"#{conv_class} selected"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module ErrorMessagesHelper
-
# Render error messages for the given objects. The :message and :header_message options are allowed.
-
def error_messages_for(*objects)
-
options = objects.extract_options!
-
options[:message] ||= I18n.t('error_messages.helper.correct_the_following_errors_and_try_again')
-
messages = objects.compact.map { |o| o.errors.full_messages }.flatten
-
unless messages.empty?
-
content_tag(:div, class: "text-danger") do
-
list_items = messages.map { |msg| content_tag(:li, msg) }
-
content_tag(:h2, options[:header_message]) + content_tag(:p, options[:message]) + content_tag(:ul, list_items.join.html_safe)
-
end
-
end
-
end
-
-
module FormBuilderAdditions
-
def error_messages(options = {})
-
@template.error_messages_for(@object, options)
-
end
-
end
-
end
-
-
ActionView::Helpers::FormBuilder.send(:include, ErrorMessagesHelper::FormBuilderAdditions)
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module GettingStartedHelper
-
# @return [Boolean] The user has completed all steps in getting started
-
def has_completed_getting_started?
-
current_user.getting_started == false
-
end
-
end
-
# frozen_string_literal: true
-
-
module GonHelper
-
def gon_load_contact(contact)
-
Gon.preloads[:contacts] ||= []
-
if Gon.preloads[:contacts].none? {|stored_contact| stored_contact[:person][:id] == contact.person_id }
-
Gon.preloads[:contacts] << ContactPresenter.new(contact, current_user).full_hash_with_person
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module InterimStreamHackinessHelper
-
##### These methods need to go away once we pass publisher object into the partial ######
-
def publisher_formatted_text
-
if params[:prefill].present?
-
params[:prefill]
-
elsif defined?(@stream)
-
@stream.publisher.prefill
-
else
-
nil
-
end
-
end
-
-
def from_group(post)
-
if defined?(@stream) && params[:controller] == 'multis'
-
@stream.post_from_group(post)
-
else
-
[]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module InvitationCodesHelper
-
def invite_hidden_tag(invite)
-
if invite.present?
-
hidden_field_tag 'invite[token]', invite.token
-
end
-
end
-
-
def invite_link(invite_code)
-
text_field_tag :invite_code, invite_code_url(invite_code), class: "form-control", readonly: true
-
end
-
-
def invited_by_message
-
inviter = current_user.invited_by
-
if inviter.present?
-
@person = inviter.person
-
render partial: "people/add_contact"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module LanguageHelper
-
include ApplicationHelper
-
-
def available_language_options
-
options = []
-
AVAILABLE_LANGUAGES.each do |locale, language|
-
options << [language, locale]
-
end
-
options.sort_by { |o| o[0] }
-
end
-
-
def get_javascript_strings_for(language, section)
-
translations = I18n.t(section, locale: DEFAULT_LANGUAGE).dup
-
translations.deep_merge!(I18n.t(section, locale: language)) if language != DEFAULT_LANGUAGE
-
-
translations["pluralization_rule"] = I18n.t("i18n.plural.js_rule", locale: language)
-
translations["pod_name"] = pod_name
-
translations
-
end
-
-
def direction_for(string)
-
return '' unless string.respond_to?(:cleaned_is_rtl?)
-
string.cleaned_is_rtl? ? 'rtl' : 'ltr'
-
end
-
-
def rtl?
-
@rtl ||= RTL_LANGUAGES.include?(I18n.locale.to_s)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
# These helper methods can be called in your template to set variables to be used in the layout
-
# This module should be included in all views globally,
-
# to do so you may need to add this line to your ApplicationController
-
# helper :layout
-
module LayoutHelper
-
include ApplicationHelper
-
-
def title(page_title, show_title = true)
-
content_for(:title) { page_title.to_s }
-
@show_title = show_title
-
end
-
-
def page_title(text=nil)
-
return text unless text.blank?
-
pod_name
-
end
-
-
def load_javascript_locales(section = 'javascripts')
-
nonced_javascript_tag do
-
<<-JS.html_safe
-
Diaspora.I18n.load(#{get_javascript_strings_for(I18n.locale, section).to_json},
-
"#{I18n.locale}",
-
#{get_javascript_strings_for(DEFAULT_LANGUAGE, section).to_json});
-
Diaspora.Page = "#{params[:controller].camelcase}#{params[:action].camelcase}";
-
JS
-
end
-
end
-
-
def current_user_atom_tag
-
return unless @person.present?
-
content_tag(:link, "", rel: "alternate", href: @person.atom_url, type: "application/atom+xml",
-
title: t("layouts.application.public_feed", name: @person.name))
-
end
-
-
def translation_missing_warnings
-
return if Rails.env == "production"
-
-
content_tag(:style) do
-
<<-CSS
-
.translation_missing { color: purple; background-color: red; }
-
CSS
-
end
-
end
-
-
def include_color_theme(view="desktop")
-
stylesheet_link_tag "#{current_color_theme}/#{view}", media: "all"
-
end
-
-
def flash_messages
-
flash.map do |name, msg|
-
klass = flash_class name
-
content_tag(:div, msg, class: "flash-body expose") do
-
content_tag(:div, msg, class: "flash-message message alert alert-#{klass}", role: "alert")
-
end
-
end.join(' ').html_safe
-
end
-
end
-
# frozen_string_literal: true
-
-
module MetaDataHelper
-
include ActionView::Helpers::AssetUrlHelper
-
include ActionView::Helpers::TagHelper
-
-
def og_prefix
-
"og: https://ogp.me/ns# article: https://ogp.me/ns/article# profile: https://ogp.me/ns/profile#"
-
end
-
-
def site_url
-
AppConfig.environment.url
-
end
-
-
def default_image_url
-
asset_url("assets/branding/logos/asterisk.png", skip_pipeline: true)
-
end
-
-
def default_author_name
-
AppConfig.settings.pod_name
-
end
-
-
def default_description
-
AppConfig.settings.default_metas.description
-
end
-
-
def default_title
-
AppConfig.settings.default_metas.title
-
end
-
-
def general_metas
-
{
-
description: {name: "description", content: default_description},
-
og_description: {property: "description", content: default_description},
-
og_site_name: {property: "og:site_name", content: default_title},
-
og_url: {property: "og:url", content: site_url},
-
og_image: {property: "og:image", content: default_image_url},
-
og_type: {property: "og:type", content: "website"}
-
}
-
end
-
-
def metas_tags(attributes_list={}, with_general_metas=true)
-
attributes_list = general_metas.merge(attributes_list) if with_general_metas
-
attributes_list.map {|_, attributes| meta_tag attributes }.join("\n").html_safe
-
end
-
-
# recursively calls itself if attribute[:content] is an array
-
# (metas such as og:image or og:tag can be present multiple times with different values)
-
def meta_tag(attributes)
-
return "" if attributes.empty?
-
return tag(:meta, attributes) unless attributes[:content].respond_to?(:to_ary)
-
items = attributes.delete(:content)
-
items.map {|item| meta_tag(attributes.merge(content: item)) }.join("\n")
-
end
-
end
-
# frozen_string_literal: true
-
-
module MobileHelper
-
def mobile_reshare_icon(post)
-
if (post.public? || reshare?(post)) && (user_signed_in? && post.author != current_user.person)
-
absolute_root = reshare?(post) ? post.absolute_root : post
-
-
if absolute_root && absolute_root.author != current_user.person
-
reshare = Reshare.where(author_id: current_user.person_id,
-
root_guid: absolute_root.guid).first
-
klass = reshare.present? ? "active" : "inactive"
-
link_to content_tag(:span, post.reshares.size, class: "count reshare-count"),
-
reshares_path(root_guid: absolute_root.guid),
-
title: t("reshares.reshare.reshare_confirmation", author: absolute_root.author_name),
-
class: "entypo-reshare reshare-action #{klass}"
-
else
-
content_tag :div,
-
content_tag(:span, post.reshares.size, class: "count reshare-count"),
-
class: "entypo-reshare reshare-action disabled"
-
end
-
else
-
content_tag :div,
-
content_tag(:span, post.reshares.size, class: "count reshare-count"),
-
class: "entypo-reshare reshare-action disabled"
-
end
-
end
-
-
def mobile_like_icon(post)
-
if current_user&.liked?(post)
-
link_to content_tag(:span, post.likes.size, class: "count like-count"),
-
"#",
-
data: {url: post_like_path(post.id, current_user.like_for(post).id)},
-
class: "entypo-heart like-action active"
-
else
-
link_to content_tag(:span, post.likes.size, class: "count like-count"),
-
"#",
-
data: {url: post_likes_path(post.id)},
-
class: "entypo-heart like-action inactive"
-
end
-
end
-
-
def mobile_like_comment_icon(comment)
-
if current_user&.liked?(comment)
-
link_to content_tag(:span, comment.likes.size, class: "count like-count"),
-
"#",
-
data: {url: comment_like_path(comment.id, current_user.like_for(comment).id)},
-
class: "entypo-heart like-action active"
-
else
-
link_to content_tag(:span, comment.likes.size, class: "count like-count"),
-
"#",
-
data: {url: comment_likes_path(comment.id)},
-
class: "entypo-heart like-action inactive"
-
end
-
end
-
-
def mobile_comment_icon(post)
-
link_to content_tag(:span, post.comments.size, class: "count comment-count"),
-
new_post_comment_path(post),
-
class: "entypo-comment comment-action inactive"
-
end
-
-
def show_comments_link(post, klass="")
-
if klass == "active"
-
entypo_class = "entypo-chevron-up"
-
else
-
entypo_class = "entypo-chevron-down"
-
end
-
-
link_to safe_join([
-
t("admins.stats.comments", count: post.comments_count),
-
content_tag(:i, nil, class: entypo_class)
-
]),
-
post_comments_path(post, format: "mobile"),
-
class: "show-comments #{klass}"
-
end
-
-
def additional_photos
-
if photo.status_message_guid?
-
@additional_photos ||= photo.status_message.photos.order(:created_at)
-
end
-
end
-
-
def next_photo
-
@next_photo ||= additional_photos[additional_photos.index(photo)+1]
-
@next_photo ||= additional_photos.first
-
end
-
-
def previous_photo
-
@previous_photo ||= additional_photos[additional_photos.index(photo)-1]
-
end
-
-
def photo
-
@photo ||= current_user.find_visible_shareable_by_id(Photo, params[:id])
-
end
-
end
-
# frozen_string_literal: true
-
-
module NotificationsHelper
-
include PeopleHelper
-
include PostsHelper
-
-
def object_link(note, actors_html)
-
target_type = note.popup_translation_key
-
opts = {actors: actors_html, count: note.actors.size}
-
-
if note.respond_to?(:linked_object)
-
if note.linked_object.nil? && note.respond_to?(:deleted_translation_key)
-
target_type = note.deleted_translation_key
-
elsif note.is_a?(Notifications::Mentioned)
-
opts.merge!(opts_for_mentioned(note.linked_object))
-
elsif %w(Notifications::CommentOnPost Notifications::AlsoCommented Notifications::Reshared Notifications::Liked)
-
.include?(note.type)
-
opts.merge!(opts_for_post(note.linked_object))
-
elsif note.is_a?(Notifications::LikedComment)
-
opts.merge!(opts_for_comment(note.linked_object))
-
elsif note.is_a?(Notifications::ContactsBirthday)
-
opts.merge!(opts_for_birthday(note))
-
end
-
end
-
translation(target_type, **opts)
-
end
-
-
def translation(target_type, **kwargs)
-
t(target_type, **kwargs).html_safe # rubocop:disable Rails/OutputSafety
-
end
-
-
def opts_for_post(post)
-
{
-
post_author: html_escape(post.author_name),
-
post_link: link_to(post_page_title(post),
-
post_path(post),
-
data: {ref: post.id},
-
class: "hard_object_link")
-
}
-
end
-
-
def opts_for_comment(comment)
-
{
-
comment_link: link_to(comment.message.title,
-
post_path(comment.post, anchor: comment.guid),
-
data: {ref: comment.id},
-
class: "hard_object_link")
-
}
-
end
-
-
def opts_for_mentioned(mentioned)
-
post = mentioned.instance_of?(Comment) ? mentioned.parent : mentioned
-
{
-
post_link: link_to(post_page_title(post), post_path(post)).html_safe
-
}.tap {|opts|
-
opts[:comment_path] = post_path(post, anchor: mentioned.guid).html_safe if mentioned.instance_of?(Comment)
-
}
-
end
-
-
def opts_for_birthday(note)
-
{date: I18n.l(note.created_at, format: I18n.t("date.formats.fullmonth_day"))}
-
end
-
-
def notification_people_link(note, people=nil)
-
actors =people || note.actors
-
number_of_actors = actors.size
-
sentence_translations = {:two_words_connector => " #{t('notifications.index.and')} ", :last_word_connector => ", #{t('notifications.index.and')} " }
-
actor_links = actors.collect{ |person|
-
person_link(person, :class => 'hovercardable')
-
}
-
-
if number_of_actors < 4
-
message = actor_links.to_sentence(sentence_translations)
-
else
-
first, second, third, *others = actor_links
-
others_sentence = others.to_sentence(sentence_translations)
-
if others.count == 1
-
others_sentence = " #{t('notifications.index.and')} " + others_sentence
-
end
-
message = "#{first}, #{second}, #{third},"
-
message += "<a class='more' href='#'> #{t('notifications.index.and_others', :count =>(number_of_actors - 3))}</a>"
-
message += "<span class='hidden'> #{others_sentence} </span>"
-
end
-
message.html_safe
-
end
-
-
def notification_message_for(note)
-
object_link(note, notification_people_link(note))
-
end
-
-
def the_day(date)
-
date.split('-')[2].to_i
-
end
-
-
def the_month(date)
-
I18n.l(Date.strptime(date, '%Y-%m-%d'), :format => '%B')
-
end
-
-
def the_year(date)
-
date.split('-')[0].to_i
-
end
-
-
def locale_date(date)
-
I18n.l(Date.strptime(date, '%Y-%m-%d'), :format => I18n.t('date.formats.fullmonth_day'))
-
end
-
-
def display_year?(year, date)
-
unless year
-
Date.current.year != the_year(date)
-
else
-
year != the_year(date)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module NotifierHelper
-
include PostsHelper
-
-
# @param post [Post] The post object.
-
# @param opts [Hash] Optional hash. Accepts :html parameter.
-
# @return [String] The formatted post.
-
def post_message(post, opts={})
-
rendered = opts[:html] ? post.message&.markdownified_for_mail : post.message&.plain_text_without_markdown
-
rendered.presence || post_page_title(post)
-
end
-
-
# @param comment [Comment] The comment to process.
-
# @param opts [Hash] Optional hash. Accepts :html parameter.
-
# @return [String] The formatted comment.
-
def comment_message(comment, opts={})
-
if comment.post.public?
-
opts[:html] ? comment.message.markdownified_for_mail : comment.message.plain_text_without_markdown
-
else
-
I18n.translate "notifier.a_limited_post_comment"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module OEmbedHelper
-
def o_embed_html(cache)
-
data = cache.data
-
data = {} if data.blank?
-
title = data.fetch('title', cache.url)
-
html = link_to(title, cache.url, :target => '_blank')
-
return html unless data.has_key?('type')
-
case data['type']
-
when 'video', 'rich'
-
if cache.is_trusted_and_has_html?
-
html = data['html']
-
elsif data.has_key?('thumbnail_url')
-
html = link_to_oembed_image(cache)
-
end
-
when 'photo'
-
if data.has_key?('url')
-
img_options = cache.options_hash('')
-
html = link_to_oembed_image(cache, '')
-
end
-
else
-
end
-
-
return html.gsub('http://', 'https://').html_safe
-
end
-
-
def link_to_oembed_image(cache, prefix = 'thumbnail_')
-
link_to(oembed_image_tag(cache, prefix), cache.url, :target => '_blank')
-
end
-
-
def oembed_image_tag(cache, prefix)
-
image_tag(cache.data[prefix + 'url'], cache.options_hash(prefix))
-
end
-
end
-
# frozen_string_literal: true
-
-
module OpenGraphHelper
-
def link_to_oembed_image(cache, prefix = 'thumbnail_')
-
link_to(oembed_image_tag(cache, prefix), cache.url, :target => '_blank')
-
end
-
-
def oembed_image_tag(cache, prefix)
-
image_tag(cache.data["#{prefix}url"], cache.options_hash(prefix))
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module PeopleHelper
-
include ERB::Util
-
-
def search_header
-
if search_query.blank?
-
content_tag(:h2, t('people.index.no_results'))
-
else
-
content_tag(:h2, id: 'search_title') do
-
t('people.index.results_for', search_term: content_tag(:span, search_query, class: 'term')).html_safe + looking_for_tag_link
-
end
-
end
-
end
-
-
def birthday_format(bday)
-
if bday.year <= 1004
-
I18n.l bday, format: I18n.t("date.formats.birthday")
-
else
-
I18n.l bday, format: I18n.t("date.formats.birthday_with_year")
-
end
-
end
-
-
def person_link(person, opts={})
-
css_class = person_link_class(person, opts[:class])
-
remote_or_hovercard_link = person_path(person)
-
tag.a('data-hovercard': remote_or_hovercard_link, href: remote_or_hovercard_link, class: css_class) do
-
opts[:display_name] || person.name
-
end
-
end
-
-
def person_image_tag(person, size = :thumb_small)
-
return "" if person.nil? || person.profile.nil?
-
image_tag(person.profile.image_url(size: size), alt: person.name, class: "avatar img-responsive center-block",
-
title: person.name, "data-person_id": person.id)
-
end
-
-
def person_image_link(person, opts={})
-
return "" if person.nil? || person.profile.nil?
-
if opts[:to] == :photos
-
link_to person_image_tag(person, opts[:size]), person_photos_path(person)
-
else
-
tag.a(href: person_path(person), class: person_link_class(person, opts[:class])) do
-
person_image_tag(person, opts[:size])
-
end
-
end
-
end
-
-
def local_or_remote_person_path(person, opts={})
-
opts.merge!(:protocol => AppConfig.pod_uri.scheme, :host => AppConfig.pod_uri.authority)
-
absolute = opts.delete(:absolute)
-
-
if person.local?
-
username = person.username
-
unless username.include?('.')
-
opts.merge!(:username => username)
-
return absolute ? user_profile_url(opts) : user_profile_path(opts)
-
end
-
end
-
-
absolute ? person_url(person, opts) : person_path(person, opts)
-
end
-
-
private
-
-
def person_link_class(person, css_class)
-
return css_class unless defined?(user_signed_in?) && user_signed_in?
-
-
return "#{css_class} self" if current_user.person == person
-
-
"#{css_class} hovercardable"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module PostsHelper
-
def post_page_title(post, opts={})
-
if post.is_a?(Photo)
-
I18n.t "posts.show.photos_by", :count => 1, :author => post.status_message_author_name
-
elsif post.is_a?(Reshare)
-
I18n.t "posts.show.reshare_by", :author => post.author_name
-
else
-
if post.message.present?
-
post.message.title opts
-
elsif post.respond_to?(:photos) && post.photos.present?
-
I18n.t "posts.show.photos_by", :count => post.photos.size, :author => post.author_name
-
end
-
end
-
end
-
-
def post_iframe_url(post_id, opts={})
-
opts[:width] ||= 516
-
opts[:height] ||= 315
-
"<iframe src='#{AppConfig.url_to(Rails.application.routes.url_helpers.post_path(post_id))}' " \
-
"width='#{opts[:width]}px' height='#{opts[:height]}px' frameBorder='0'></iframe>".html_safe
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module ProfileHelper
-
def upper_limit_date_of_birth
-
minimum_year = AppConfig.settings.terms.minimum_age.get
-
minimum_year = minimum_year ? minimum_year.to_i : 13
-
minimum_year.years.ago.year
-
end
-
-
def lower_limit_date_of_birth
-
125.years.ago.year
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module PublisherHelper
-
def available_services
-
current_user.services.select {|service| AppConfig.configured_services.map(&:to_s).include? service.provider }
-
end
-
-
def service_button(service)
-
provider_title = I18n.t("services.index.share_to", provider: service.provider.titleize)
-
content_tag :div,
-
class: "btn btn-link service_icon dim",
-
title: "#{provider_title} (#{service.nickname})",
-
id: service.provider,
-
maxchar: service.class::MAX_CHARACTERS,
-
data: {toggle: "tooltip", placement: "bottom"} do
-
if service.provider == "wordpress"
-
content_tag(:span, "", class: "social-media-logos-wordpress-16x16")
-
else
-
content_tag(:i, "", class: "entypo-social-#{service.provider} small")
-
end
-
end
-
end
-
-
def public_selected?(selected_aspects)
-
"public" == selected_aspects.try(:first) || publisher_boolean?(:public)
-
end
-
-
def all_aspects_selected?(selected_aspects)
-
!all_aspects.empty? && all_aspects.size == selected_aspects.size && !public_selected?(selected_aspects)
-
end
-
-
def aspect_selected?(aspect, selected_aspects)
-
selected_aspects.include?(aspect) && !all_aspects_selected?(selected_aspects) && !public_selected?(selected_aspects)
-
end
-
-
def publisher_open?
-
publisher_boolean?(:open)
-
end
-
-
private
-
-
def publisher_boolean?(option)
-
@stream.try(:publisher).try(option) == true
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module ReportHelper
-
def report_content(report)
-
case (item = report.item)
-
when Post
-
raw t("report.post_label", content: link_to(post_message(item), post_path(item.id)))
-
when Comment
-
raw t("report.comment_label", data: link_to(
-
h(comment_message(item)),
-
post_path(item.post.id, anchor: item.guid)
-
))
-
else
-
t("report.not_found")
-
end
-
end
-
-
def unreviewed_reports_count
-
@unreviewed_reports_count ||= Report.where(reviewed: false).size
-
end
-
end
-
# frozen_string_literal: true
-
-
module ServicesHelper
-
def contact_proxy(friend)
-
friend.contact || contact_proxy_template.dup.tap {|c| c.person = friend.person }
-
end
-
-
private
-
-
def contact_proxy_template
-
@@contact_proxy ||= Contact.new(:aspects => [])
-
end
-
end
-
# frozen_string_literal: true
-
-
module SessionsHelper
-
def prefilled_username
-
uri = Addressable::URI.parse(session["user_return_to"])
-
uri&.query_values&.fetch("login_hint", "")
-
end
-
-
def display_registration_link?
-
AppConfig.settings.enable_registrations? && controller_name != "registrations"
-
end
-
-
def display_password_reset_link?
-
AppConfig.mail.enable? && devise_mapping.recoverable? && controller_name != "passwords"
-
end
-
-
def flash_class(name)
-
{notice: "success", alert: "danger", error: "danger"}[name.to_sym]
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module StatisticsHelper
-
def registrations_status statistics
-
if statistics.open_registrations?
-
I18n.t('statistics.open')
-
else
-
I18n.t('statistics.closed')
-
end
-
end
-
-
def registrations_status_class statistics
-
if statistics.open_registrations?
-
"serv-enabled"
-
else
-
"serv-disabled"
-
end
-
end
-
-
def service_status service, available_services
-
if available_services.include? service.to_s
-
I18n.t('statistics.enabled')
-
else
-
I18n.t('statistics.disabled')
-
end
-
end
-
-
def service_class service, available_services
-
if available_services.include? service.to_s
-
"serv-enabled"
-
else
-
"serv-disabled"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module StreamHelper
-
def next_page_path(opts ={})
-
if controller.instance_of?(TagsController)
-
tag_path(:name => @stream.tag_name, :max_time => time_for_scroll(@stream))
-
elsif controller.instance_of?(PeopleController)
-
local_or_remote_person_path(@person, :max_time => time_for_scroll(@stream))
-
elsif controller.instance_of?(StreamsController)
-
next_stream_path
-
else
-
raise 'in order to use pagination for this new controller, update next_page_path in stream helper'
-
end
-
end
-
-
def reshare?(post)
-
post.instance_of?(Reshare)
-
end
-
-
private
-
# rubocop:disable Rails/HelperInstanceVariable
-
def next_stream_path
-
if current_page?(:stream)
-
stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:activity_stream)
-
activity_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:aspects_stream)
-
aspects_stream_path(max_time: time_for_scroll(@stream), a_ids: session[:a_ids])
-
elsif current_page?(:local_public_stream)
-
local_public_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:public_stream)
-
public_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:commented_stream)
-
commented_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:liked_stream)
-
liked_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:mentioned_stream)
-
mentioned_stream_path(max_time: time_for_scroll(@stream))
-
elsif current_page?(:followed_tags_stream)
-
followed_tags_stream_path(max_time: time_for_scroll(@stream))
-
else
-
raise "in order to use pagination for this new stream, update next_stream_path in stream helper"
-
end
-
end
-
# rubocop:enable Rails/HelperInstanceVariable
-
-
def time_for_scroll(stream)
-
if stream.stream_posts.empty?
-
(Time.now() + 1).to_i
-
else
-
stream.stream_posts.last.send(stream.order.to_sym).to_i
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module TagsHelper
-
def looking_for_tag_link
-
return if search_query.include?('@') || normalize_tag_name(search_query).blank?
-
content_tag('small') do
-
t('people.index.looking_for', tag_link: tag_link(search_query)).html_safe
-
end
-
end
-
-
def normalize_tag_name(tag)
-
ActsAsTaggableOn::Tag.normalize(tag.to_s)
-
end
-
-
def tag_link(tag)
-
link_to("##{tag}", tag_path(name: normalize_tag_name(tag)))
-
end
-
end
-
# frozen_string_literal: true
-
-
module UserApplicationsHelper
-
def user_application_name(app)
-
if app.name?
-
"#{html_escape app.name} (#{link_to(app.url, app.url)})"
-
else
-
link_to(app.url, app.url)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module UsersHelper
-
def owner_image_tag(size=nil)
-
person_image_tag(current_user.person, size)
-
end
-
-
def owner_image_link
-
person_image_link(current_user.person, :size => :thumb_small)
-
end
-
-
# Returns the path of the current color theme so that it
-
# can be loaded in app/views/layouts/application.html.haml
-
# and app/views/layouts/application.mobile.haml. If the user
-
# is not signed in or has not specified a color theme, the
-
# default (original) color theme is loaded.
-
#
-
# @example if user is not signed in
-
# current_color_theme #=> "color_themes/original"
-
# @example if user Alice has not selected a color theme
-
# current_color_theme #=> "color_themes/original"
-
# @example if user Alice has selected a "magenta" theme
-
# current_color_theme #=> "color_themes/magenta"
-
def current_color_theme
-
if user_signed_in?
-
color_theme = current_user.color_theme
-
end
-
color_theme ||= AppConfig.settings.default_color_theme
-
"color_themes/#{color_theme}"
-
end
-
-
# Returns an array of the color themes available, as
-
# specified from AVAILABLE_COLOR_THEMES in
-
# config/initializers/color_themes.rb.
-
#
-
# @example if AVAILABLE_COLOR_THEMES = ["original", "dark_green"]
-
# available_color_themes
-
# #=> [["Original gray", "original"], ["Dark green", "dark_green"]]
-
def available_color_themes
-
opts = []
-
AVAILABLE_COLOR_THEMES.map do |theme_code|
-
opts << [I18n.t("color_themes.#{theme_code}"), theme_code]
-
end
-
opts
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ApplicationMailer < ActionMailer::Base
-
1
default from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>"
-
end
-
# frozen_string_literal: true
-
-
1
class DiasporaDeviseMailer < Devise::Mailer
-
1
default from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>"
-
-
1
def self.mailer_name
-
"devise/mailer"
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ExportMailer < ApplicationMailer
-
1
def export_complete_for(user)
-
4
send_mail(user, I18n.t("notifier.export_email.subject", name: user.name),
-
I18n.t("notifier.export_email.body", url: download_profile_user_url, name: user.first_name))
-
end
-
-
1
def export_failure_for(user)
-
8
send_mail(user, I18n.t("notifier.export_failure_email.subject", name: user.name),
-
I18n.t("notifier.export_failure_email.body", name: user.first_name))
-
end
-
-
1
def export_photos_complete_for(user)
-
4
send_mail(user, I18n.t("notifier.export_photos_email.subject", name: user.name),
-
I18n.t("notifier.export_photos_email.body", url: download_photos_user_url, name: user.first_name))
-
end
-
-
1
def export_photos_failure_for(user)
-
5
send_mail(user, I18n.t("notifier.export_photos_failure_email.subject", name: user.name),
-
I18n.t("notifier.export_photos_failure_email.body", name: user.first_name))
-
end
-
-
1
private
-
-
1
def send_mail(user, subject, body)
-
21
mail(to: user.email, subject: subject) do |format|
-
42
format.html { render "notifier/plain_markdown_email", locals: {body: body} }
-
42
format.text { render "notifier/plain_markdown_email", locals: {body: body} }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Maintenance < ApplicationMailer
-
1
def account_removal_warning(user)
-
7
I18n.with_locale(user.language) do
-
7
body = I18n.t("notifier.remove_old_user.body",
-
pod_url: AppConfig.environment.url,
-
login_url: new_user_session_url,
-
after_days: AppConfig.settings.maintenance.remove_old_users.after_days.to_s,
-
remove_after: user.remove_after)
-
7
mail(to: user.email, subject: I18n.t("notifier.remove_old_user.subject")) do |format|
-
14
format.text { render "notifier/plain_markdown_email", locals: {body: body} }
-
14
format.html { render "notifier/plain_markdown_email", locals: {body: body} }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class AlsoCommented < NotificationMailers::Base
-
1
attr_accessor :comment
-
1
delegate :post, to: :comment, prefix: true
-
-
1
def set_headers(comment_id)
-
16
@comment = Comment.find_by_id(comment_id)
-
-
16
if mail?
-
16
@headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
-
16
if @comment.public?
-
7
@headers[:subject] = "Re: #{@comment.comment_email_subject}"
-
else
-
9
@headers[:subject] = I18n.t("notifier.also_commented.limited_subject")
-
end
-
end
-
end
-
-
1
def mail?
-
16
@recipient && @sender && @comment
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class Base
-
1
include Diaspora::Logging
-
-
1
attr_accessor :recipient, :sender
-
-
1
delegate :unconfirmed_email, :confirm_email_token,
-
:first_name, to: :recipient, prefix: true
-
1
delegate :first_name, :name, :sender, to: :sender, prefix: true
-
-
1
def initialize(recipient_id, sender_id=nil, *args)
-
734
@headers = {}
-
734
@recipient = User.find(recipient_id)
-
734
@sender = Person.find(sender_id) if sender_id.present?
-
-
734
log_mail(recipient_id, sender_id, self.class.to_s.underscore)
-
-
734
with_recipient_locale do
-
734
set_headers(*args)
-
end
-
end
-
-
1
def headers
-
734
default_headers.merge(@headers)
-
end
-
-
1
def name_and_address(name, email)
-
1474
address = Mail::Address.new Addressable::IDNA.to_ascii(email)
-
1474
address.display_name = name
-
1474
address.format
-
end
-
-
1
private
-
-
1
def default_headers
-
734
from_name = AppConfig.settings.pod_name
-
734
from_name += " (#{person_name(@sender)})" if @sender.present?
-
-
{
-
734
from: name_and_address(from_name, AppConfig.mail.sender_address),
-
to: name_and_address(person_name(@recipient), @recipient.email),
-
template_name: self.class.name.demodulize.underscore
-
}
-
end
-
-
1
def person_name(person)
-
1454
if person.profile.full_name.empty?
-
1
person.username
-
else
-
1453
person.name.gsub(/\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Presentation}/, "").strip
-
end
-
end
-
-
1
def with_recipient_locale(&block)
-
734
I18n.with_locale(@recipient.language, &block)
-
end
-
-
1
def log_mail(recipient_id, sender_id, type)
-
734
log_string = "event=mail mail_type=#{type} recipient_id=#{recipient_id} sender_id=#{sender_id} " \
-
" recipient_handle=#{@recipient.diaspora_handle}"
-
734
log_string = "#{log_string} sender_handle=#{@sender.diaspora_handle}" if sender_id.present?
-
-
734
logger.info log_string
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class CommentOnPost < NotificationMailers::Base
-
1
attr_accessor :comment
-
-
1
def set_headers(comment_id)
-
24
@comment = Comment.find(comment_id)
-
-
24
@headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
-
24
if @comment.public?
-
13
@headers[:subject] = "Re: #{@comment.comment_email_subject}"
-
else
-
11
@headers[:subject] = I18n.t("notifier.comment_on_post.limited_subject")
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class ConfirmEmail < NotificationMailers::Base
-
1
def set_headers
-
6
@headers[:to] = name_and_address(@recipient.first_name, @recipient.unconfirmed_email)
-
6
@headers[:subject] = I18n.t('notifier.confirm_email.subject', :unconfirmed_email => @recipient.unconfirmed_email)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class ContactsBirthday < NotificationMailers::Base
-
1
attr_accessor :person
-
-
1
def set_headers(person_id)
-
3
@person = Person.find(person_id)
-
3
@headers[:subject] = I18n.t("notifier.contacts_birthday.subject", name: @person.name)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class CsrfTokenFail < NotificationMailers::Base
-
1
def set_headers
-
5
@headers[:subject] = I18n.t("notifier.csrf_token_fail.subject", name: @recipient.name)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class Liked < NotificationMailers::Base
-
1
attr_accessor :like
-
1
delegate :target, to: :like, prefix: true
-
-
1
def set_headers(like_id)
-
15
@like = Like.find(like_id)
-
-
15
@headers[:subject] = I18n.t('notifier.liked.liked', :name => @sender.name)
-
15
@headers[:in_reply_to] = @headers[:references] = "<#{@like.parent.guid}@#{AppConfig.pod_uri.host}>"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class LikedComment < NotificationMailers::Base
-
1
attr_accessor :like
-
-
1
delegate :target, to: :like, prefix: true
-
-
1
def set_headers(like_id) # rubocop:disable Naming/AccessorMethodName
-
10
@like = Like.find(like_id)
-
-
10
@headers[:subject] = I18n.t("notifier.liked_comment.liked", name: @sender.name)
-
10
@headers[:in_reply_to] = @headers[:references] = "<#{@like.parent.commentable.guid}@#{AppConfig.pod_uri.host}>"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class Mentioned < NotificationMailers::Base
-
1
attr_accessor :post
-
1
delegate :author_name, to: :post, prefix: true
-
-
1
def set_headers(target_id)
-
108
@post = Mention.find_by_id(target_id).mentions_container
-
-
108
@headers[:subject] = I18n.t('notifier.mentioned.subject', :name => @sender.name)
-
108
@headers[:in_reply_to] = @headers[:references] = "<#{@post.guid}@#{AppConfig.pod_uri.host}>"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class MentionedInComment < NotificationMailers::Base
-
1
attr_reader :comment
-
-
1
def set_headers(target_id) # rubocop:disable Naming/AccessorMethodName
-
20
@comment = Mention.find_by_id(target_id).mentions_container
-
-
20
@headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
-
20
@headers[:subject] = I18n.t("notifier.mentioned.subject", name: @sender.name)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class PrivateMessage < NotificationMailers::Base
-
1
attr_accessor :message, :conversation, :participants
-
-
1
def set_headers(message_id)
-
7
@message = Message.find_by_id(message_id)
-
7
@conversation = @message.conversation
-
7
@participants = @conversation.participants
-
-
7
@headers[:subject] = I18n.t("notifier.private_message.subject")
-
7
@headers[:in_reply_to] = @headers[:references] = "<#{@conversation.guid}@#{AppConfig.pod_uri.host}>"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class Reshared < NotificationMailers::Base
-
1
attr_accessor :reshare
-
-
1
delegate :root, to: :reshare, prefix: true
-
-
1
def set_headers(reshare_id)
-
6
@reshare = Reshare.find(reshare_id)
-
-
6
@headers[:subject] = I18n.t('notifier.reshared.reshared', :name => @sender.name)
-
6
@headers[:in_reply_to] = @headers[:references] = "<#{@reshare.root_guid}@#{AppConfig.pod_uri.host}>"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module NotificationMailers
-
1
class StartedSharing < NotificationMailers::Base
-
1
def set_headers(*_args) # rubocop:disable Naming/AccessorMethodName
-
514
@headers[:subject] = I18n.t("notifier.started_sharing.subject", name: @sender.name)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Notifier < ApplicationMailer
-
1
helper :application
-
1
helper :notifier
-
1
helper :people
-
-
1
def self.admin(string, recipients, opts = {}, subject=nil)
-
3
mails = []
-
3
recipients.each do |rec|
-
11
mail = single_admin(string, rec, opts.dup, subject)
-
11
mails << mail
-
end
-
3
mails
-
end
-
-
1
def single_admin(string, recipient, opts={}, subject=nil)
-
14
@receiver = recipient
-
14
@string = string.html_safe
-
-
14
if attach = opts.delete(:attachments)
-
6
attach.each{ |f|
-
6
attachments[f[:name]] = f[:file]
-
}
-
end
-
-
14
subject ||= I18n.t("notifier.single_admin.subject")
-
-
14
default_opts = {to: @receiver.email, from: AppConfig.mail.sender_address, subject: subject}
-
14
default_opts.merge!(opts)
-
-
14
mail(default_opts)
-
end
-
-
1
def invite(email, inviter, invitation_code, locale)
-
9
I18n.with_locale(locale) do
-
9
mail_opts = {to: email, from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>",
-
subject: I18n.t("notifier.invited_you", name: inviter.name)}
-
9
name = inviter.full_name.empty? ? inviter.diaspora_handle : "#{inviter.name} (#{inviter.diaspora_handle})"
-
9
body = I18n.t("notifier.invite.message",
-
invite_url: invite_code_url(invitation_code),
-
diasporafoundation_url: "https://diasporafoundation.org/",
-
user: name,
-
diaspora_id: inviter.diaspora_handle)
-
-
9
mail(mail_opts) do |format|
-
18
format.text { render "notifier/plain_markdown_email", layout: nil, locals: {body: body} }
-
18
format.html { render "notifier/plain_markdown_email", layout: nil, locals: {body: body} }
-
end
-
end
-
end
-
-
1
def send_notification(type, *args)
-
734
@notification = NotificationMailers.const_get(type.camelize).new(*args)
-
-
734
with_recipient_locale do
-
734
mail(@notification.headers)
-
end
-
end
-
-
1
private
-
-
1
def with_recipient_locale(&block)
-
734
I18n.with_locale(@notification.recipient.language, &block)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ReportMailer < ApplicationMailer
-
1
def self.new_report(report_id)
-
7
report = Report.find_by_id(report_id)
-
21
Role.moderators.map {|role| super(report.item_type, report.item_id, report.text, role) }
-
end
-
-
1
def new_report(type, id, reason, role)
-
resource = {
-
14
url: report_index_url,
-
type: I18n.t("notifier.report_email.type.#{type.downcase}"),
-
id: id,
-
reason: reason
-
}
-
14
person = Person.find(role.person_id)
-
14
return unless person.local?
-
14
user = User.find_by_id(person.owner_id)
-
14
return if user.user_preferences.exists?(email_type: :someone_reported)
-
14
I18n.with_locale(user.language) do
-
14
resource[:email] = user.email
-
14
format(resource)
-
end
-
end
-
-
1
private
-
-
1
def format(resource)
-
14
body = I18n.t("notifier.report_email.body", **resource)
-
14
mail(to: resource[:email], subject: I18n.t("notifier.report_email.subject", type: resource[:type])) do |format|
-
28
format.html { render "notifier/plain_markdown_email", locals: {body: body} }
-
28
format.text { render "notifier/plain_markdown_email", locals: {body: body} }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class AccountDeletion < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
-
1
scope :uncompleted, -> { where("completed_at is null") }
-
-
1
belongs_to :person
-
1
after_commit :queue_delete_account, on: :create
-
-
1
delegate :diaspora_handle, to: :person
-
-
1
def queue_delete_account
-
17
Workers::DeleteAccount.perform_async(id)
-
end
-
-
1
def perform!
-
5
Diaspora::Federation::Dispatcher.build(person.owner, self).dispatch if person.local?
-
5
AccountDeleter.new(person).perform!
-
end
-
-
1
def subscribers
-
4
person.owner.contact_people.remote | Person.who_have_reshared_a_users_posts(person.owner).remote
-
end
-
-
1
def public?
-
3
true
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class AccountMigration < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
-
1
belongs_to :old_person, class_name: "Person"
-
1
belongs_to :new_person, class_name: "Person"
-
-
1
validates :old_person, uniqueness: true
-
1
validates :new_person, presence: true
-
-
1
after_create :lock_old_user!
-
-
1
attr_accessor :old_private_key
-
1
attr_writer :old_person_diaspora_id
-
-
1
attr_accessor :archive_contacts
-
-
1
def receive(*)
-
21
perform!
-
end
-
-
1
def public?
-
41
true
-
end
-
-
1
def sender
-
105
@sender ||= old_user || ephemeral_sender
-
end
-
-
1
def perform!
-
105
raise "already performed" if performed?
-
-
104
validate_sender if locally_initiated?
-
103
tombstone_old_user_and_update_all_references if old_person
-
103
dispatch if locally_initiated?
-
103
dispatch_contacts
-
103
update(completed_at: Time.zone.now)
-
end
-
-
1
def performed?
-
110
!completed_at.nil?
-
end
-
-
# Send migration to all imported contacts, but also send it to all contacts from the archive which weren't imported,
-
# but maybe share with the old account, so they can update contact information and resend the contact message.
-
# In case when a user migrated to our pod from a remote one, we include remote person to subscribers so that
-
# the new pod is informed about the migration as well.
-
1
def subscribers
-
44
new_user.profile.subscribers.remote.to_a.tap do |subscribers|
-
44
subscribers.push(old_person) if old_person&.remote?
-
44
archive_contacts&.each do |contact|
-
8
diaspora_id = contact.fetch("account_id")
-
22
next if subscribers.any? {|s| s.diaspora_handle == diaspora_id }
-
-
3
person = Person.by_account_identifier(diaspora_id)
-
3
subscribers.push(person) if person&.remote?
-
end
-
end
-
end
-
-
# This method finds the newest user person profile in the migration chain.
-
# If person migrated multiple times then #new_person may point to a closed account.
-
# In this case in order to find open account we have to delegate new_person call to the next account_migration
-
# instance in the chain.
-
1
def newest_person
-
1055
return new_person if new_person.account_migration.nil?
-
-
190
new_person.account_migration.newest_person
-
end
-
-
1
private
-
-
# Normally pod initiates migration locally when the new user is local. Then the pod creates AccountMigration object
-
# itself. If new user is remote, then AccountMigration object is normally received via the federation and this is
-
# remote initiation then.
-
1
def remotely_initiated?
-
309
new_person.remote?
-
end
-
-
1
def locally_initiated?
-
207
!remotely_initiated?
-
end
-
-
1
def old_user
-
647
old_person&.owner
-
end
-
-
1
def new_user
-
203
new_person.owner
-
end
-
-
1
def newest_user
-
145
newest_person.owner
-
end
-
-
1
def lock_old_user!
-
123
old_user&.lock_access!
-
end
-
-
1
def user_left_our_pod?
-
128
old_user && !new_user
-
end
-
-
1
def user_changed_id_locally?
-
204
old_user && new_user
-
end
-
-
1
def includes_photo_migration?
-
61
remote_photo_path.present?
-
end
-
-
1
def tombstone_old_user_and_update_all_references
-
102
ActiveRecord::Base.transaction do
-
102
account_deleter.tombstone_person_and_profile
-
102
account_deleter.close_user if user_left_our_pod?
-
102
account_deleter.tombstone_user if user_changed_id_locally?
-
-
102
update_all_references
-
end
-
end
-
-
# We need to resend contacts of users of our pod for the remote new person so that the remote pod received this
-
# contact information from the authoritative source.
-
1
def dispatch_contacts
-
103
newest_person.contacts.sharing.each do |contact|
-
12
Diaspora::Federation::Dispatcher.defer_dispatch(contact.user, contact)
-
end
-
end
-
-
1
def dispatch
-
42
Diaspora::Federation::Dispatcher.build(sender, self).dispatch
-
end
-
-
1
EphemeralUser = Struct.new(:diaspora_handle, :serialized_private_key) do
-
1
def id
-
13
diaspora_handle
-
end
-
-
1
def encryption_key
-
13
OpenSSL::PKey::RSA.new(serialized_private_key)
-
end
-
end
-
-
1
def old_person_diaspora_id
-
28
old_person&.diaspora_handle || @old_person_diaspora_id
-
end
-
-
1
def ephemeral_sender
-
16
if old_private_key.nil? || old_person_diaspora_id.nil?
-
2
raise "can't build sender without old private key and diaspora ID defined"
-
end
-
-
14
EphemeralUser.new(old_person_diaspora_id, old_private_key)
-
end
-
-
1
def validate_sender
-
43
sender # sender method raises exception when sender can't be instantiated
-
end
-
-
1
def update_all_references
-
102
update_remote_photo_path if remotely_initiated? && includes_photo_migration?
-
102
update_person_references
-
102
update_user_references if user_changed_id_locally?
-
end
-
-
1
def person_references
-
102
references = Person.reflections.reject {|key, _|
-
2040
%w[profile owner notifications pod account_deletion account_migration].include?(key)
-
}
-
-
102
references.map {|key, value|
-
1428
{value.foreign_key => key}
-
}
-
end
-
-
1
def user_references
-
29
references = User.reflections.reject {|key, _|
-
667
%w[
-
person profile auto_follow_back_aspect invited_by aspect_memberships contact_people followed_tags
-
ignored_people conversation_visibilities pairwise_pseudonymous_identifiers conversations o_auth_applications
-
].include?(key)
-
}
-
-
29
references.map {|key, value|
-
319
{value.foreign_key => key}
-
}
-
end
-
-
1
def eliminate_person_duplicates
-
102
duplicate_person_contacts.destroy_all
-
102
duplicate_person_likes.destroy_all
-
102
duplicate_person_participations.destroy_all
-
102
duplicate_person_poll_participations.destroy_all
-
end
-
-
1
def duplicate_person_contacts
-
102
Contact
-
.joins("INNER JOIN contacts as c2 ON (contacts.user_id = c2.user_id AND contacts.person_id=#{old_person.id} AND"\
-
" c2.person_id=#{newest_person.id})")
-
end
-
-
1
def duplicate_person_likes
-
102
Like
-
.joins("INNER JOIN likes as l2 ON (likes.target_id = l2.target_id "\
-
"AND likes.target_type = l2.target_type "\
-
"AND likes.author_id=#{old_person.id} AND"\
-
" l2.author_id=#{newest_person.id})")
-
end
-
-
1
def duplicate_person_participations
-
102
Participation
-
.joins("INNER JOIN participations as p2 ON (participations.target_id = p2.target_id "\
-
"AND participations.target_type = p2.target_type "\
-
"AND participations.author_id=#{old_person.id} AND"\
-
" p2.author_id=#{newest_person.id})")
-
end
-
-
1
def duplicate_person_poll_participations
-
102
PollParticipation
-
.joins("INNER JOIN poll_participations as p2 ON (poll_participations.poll_id = p2.poll_id "\
-
"AND poll_participations.author_id=#{old_person.id} AND"\
-
" p2.author_id=#{newest_person.id})")
-
end
-
-
1
def eliminate_user_duplicates
-
29
Aspect
-
.joins("INNER JOIN aspects as a2 ON (aspects.name = a2.name AND aspects.user_id=#{old_user.id}
-
AND a2.user_id=#{newest_user.id})")
-
.destroy_all
-
29
Contact
-
.joins("INNER JOIN contacts as c2 ON (contacts.person_id = c2.person_id AND contacts.user_id=#{old_user.id} AND"\
-
" c2.user_id=#{newest_user.id})")
-
.destroy_all
-
29
TagFollowing
-
.joins("INNER JOIN tag_followings as t2 ON (tag_followings.tag_id = t2.tag_id AND"\
-
" tag_followings.user_id=#{old_user.id} AND t2.user_id=#{newest_user.id})")
-
.destroy_all
-
end
-
-
1
def update_remote_photo_path
-
26
Photo.where(author: old_person)
-
.update_all(remote_photo_path: remote_photo_path) # rubocop:disable Rails/SkipsModelValidations
-
26
return unless user_left_our_pod?
-
-
12
Photo.where(author: old_person).find_in_batches do |batch|
-
6
batch.each do |photo|
-
6
photo.processed_image = nil
-
6
photo.unprocessed_image = nil
-
6
logger.warn "Error cleaning up photo #{photo.id}" unless photo.save
-
end
-
end
-
end
-
-
1
def update_person_references
-
102
logger.debug "Updating references from person id=#{old_person.id} to person id=#{newest_person.id}"
-
102
eliminate_person_duplicates
-
102
update_references(person_references, old_person, newest_person.id)
-
end
-
-
1
def update_user_references
-
29
logger.debug "Updating references from user id=#{old_user.id} to user id=#{newest_user.id}"
-
29
eliminate_user_duplicates
-
29
update_references(user_references, old_user, newest_user.id)
-
end
-
-
1
def update_references(references, object, new_id)
-
131
references.each do |pair|
-
1747
key_id = pair.flatten[0]
-
1747
association = pair.flatten[1]
-
1747
object.send(association).update_all(key_id => new_id)
-
end
-
end
-
-
1
def account_deleter
-
155
@account_deleter ||= AccountDeleter.new(old_person)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/authorization.rb
-
-
module Api
-
module OpenidConnect
-
class Authorization < ApplicationRecord
-
belongs_to :user
-
belongs_to :o_auth_application
-
-
validates :user, uniqueness: {scope: :o_auth_application}
-
validate :validate_scope_names
-
serialize :scopes, JSON
-
-
has_many :o_auth_access_tokens, dependent: :destroy
-
-
before_validation :setup, on: :create
-
-
scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri }
-
-
SCOPES = %w[
-
contacts:modify
-
contacts:read
-
conversations
-
email
-
interactions
-
name
-
nickname
-
notifications
-
openid
-
picture
-
private:modify
-
private:read
-
profile
-
profile:modify
-
profile:read_private
-
public:modify
-
public:read
-
sub
-
tags:modify
-
tags:read
-
].freeze
-
-
def setup
-
self.refresh_token = SecureRandom.hex(32)
-
end
-
-
def validate_scope_names
-
return unless scopes
-
scopes.each do |scope|
-
errors.add(:scope, "is not a valid scope name") unless SCOPES.include? scope
-
end
-
end
-
-
# Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb#L26
-
def accessible?(required_scopes=nil)
-
Array(required_scopes).all? { |required_scope|
-
scopes.include? required_scope
-
}
-
end
-
-
def create_code
-
SecureRandom.hex(32).tap do |code|
-
update!(code: code)
-
update!(code_used: false)
-
end
-
end
-
-
def create_access_token
-
o_auth_access_tokens.create!.bearer_token
-
end
-
-
def create_id_token
-
IdToken.new(self, nonce)
-
end
-
-
def self.find_by_client_id_user_and_scopes(client_id, user, scopes)
-
app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
-
authorizations = where(o_auth_application: app, user: user).all
-
authorizations.each do |authorization|
-
if authorization.scopes.uniq.sort == Array(scopes).uniq.sort
-
return authorization
-
end
-
end
-
nil
-
end
-
-
def self.find_by_client_id_and_user(client_id, user)
-
app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
-
find_by(o_auth_application: app, user: user)
-
end
-
-
def self.find_by_refresh_token(client_id, refresh_token)
-
app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
-
find_by(o_auth_application: app, refresh_token: refresh_token)
-
end
-
-
def self.use_code(code)
-
return unless code
-
auth = find_by(code: code)
-
return unless auth
-
if auth.code_used
-
auth.destroy
-
nil
-
else
-
auth.update!(code_used: true)
-
auth
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb
-
-
1
module Api
-
1
module OpenidConnect
-
1
class OAuthAccessToken < ApplicationRecord
-
1
belongs_to :authorization
-
-
1
before_validation :setup, on: :create
-
-
1
validates :token, presence: true, uniqueness: true
-
-
477
scope :valid, ->(time) { where("expires_at >= ?", time) }
-
-
1
def setup
-
1358
self.token = SecureRandom.hex(32)
-
1358
self.expires_at = 24.hours.from_now
-
end
-
-
1
def bearer_token
-
1358
@bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new(
-
access_token: token,
-
1358
expires_in: (expires_at - Time.zone.now.utc).to_i
-
)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/app/models/client.rb
-
-
require "digest"
-
-
module Api
-
module OpenidConnect
-
class OAuthApplication < ApplicationRecord
-
has_many :authorizations, dependent: :destroy
-
has_many :user, through: :authorizations
-
-
validates :client_id, presence: true, uniqueness: true
-
validates :client_secret, presence: true
-
validates :client_name, uniqueness: {scope: :redirect_uris}
-
-
%i(redirect_uris response_types grant_types contacts jwks).each do |serializable|
-
serialize serializable, JSON
-
end
-
-
before_validation :setup, on: :create
-
before_validation do
-
redirect_uris.sort!
-
end
-
-
def as_json(opts={})
-
data = super
-
data["client_secret_expires_at"] = 0
-
data["token_endpoint_auth_method"] ||= "client_secret_post"
-
data
-
end
-
-
def setup
-
self.client_id = SecureRandom.hex(16)
-
self.client_secret = SecureRandom.hex(32)
-
end
-
-
def image_uri
-
logo_uri ? Diaspora::Camo.image_url(logo_uri) : nil
-
end
-
-
class << self
-
def available_response_types
-
["id_token", "id_token token", "code"]
-
end
-
-
def register!(registrar)
-
registrar.validate!
-
build_client_application(registrar)
-
end
-
-
private
-
-
def build_client_application(registrar)
-
attributes = registrar_attributes(registrar)
-
check_sector_identifier_uri(attributes)
-
check_redirect_uris(attributes)
-
create! attributes
-
end
-
-
def check_sector_identifier_uri(attributes)
-
sector_identifier_uri = attributes[:sector_identifier_uri]
-
return unless sector_identifier_uri
-
response = Faraday.get(sector_identifier_uri)
-
sector_identifier_uri_json = JSON.parse(response.body)
-
redirect_uris = attributes[:redirect_uris]
-
sector_identifier_uri_includes_redirect_uris = (redirect_uris - sector_identifier_uri_json).empty?
-
return if sector_identifier_uri_includes_redirect_uris
-
raise Api::OpenidConnect::Error::InvalidSectorIdentifierUri.new
-
end
-
-
def check_redirect_uris(attributes)
-
redirect_uris = attributes[:redirect_uris]
-
uri_array = redirect_uris.map {|uri| URI(uri) }
-
any_uri_contains_fragment = uri_array.any? {|uri| !uri.fragment.nil? }
-
return unless any_uri_contains_fragment
-
raise Api::OpenidConnect::Error::InvalidRedirectUri.new
-
end
-
-
def supported_metadata
-
%i(client_name response_types grant_types application_type
-
contacts logo_uri client_uri policy_uri tos_uri redirect_uris
-
sector_identifier_uri subject_type token_endpoint_auth_method jwks jwks_uri)
-
end
-
-
def registrar_attributes(registrar)
-
supported_metadata.each_with_object({}) do |key, attr|
-
value = registrar.public_send(key)
-
next unless value
-
case key
-
when :subject_type
-
attr[:ppid] = (value == "pairwise")
-
when :jwks_uri
-
response = Faraday.get(value)
-
attr[:jwks] = response.body
-
attr[:jwks_uri] = value
-
when :jwks
-
attr[:jwks] = value.to_json
-
else
-
attr[key] = value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/app/models/pairwise_pseudonymous_identifier.rb
-
-
1
module Api
-
1
module OpenidConnect
-
1
class PairwisePseudonymousIdentifier < ApplicationRecord
-
1
self.table_name = "ppid"
-
-
1
belongs_to :o_auth_application, optional: true
-
1
belongs_to :user
-
-
1
validates :identifier, presence: true, uniqueness: {scope: :user}
-
1
validates :guid, presence: true, uniqueness: true
-
-
1
before_validation :setup, on: :create
-
-
1
private
-
-
1
def setup
-
9
self.guid = SecureRandom.hex(16)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
class ApplicationRecord < ActiveRecord::Base
-
self.abstract_class = true
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Aspect < ApplicationRecord
-
1
belongs_to :user
-
-
1
has_many :aspect_memberships, :dependent => :destroy
-
1
has_many :contacts, :through => :aspect_memberships
-
-
1
has_many :aspect_visibilities, :dependent => :destroy
-
1
has_many :posts, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Post'
-
1
has_many :photos, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Photo'
-
-
1
validates :name, :presence => true, :length => { :maximum => 20 }
-
-
1
validates_uniqueness_of :name, :scope => :user_id, :case_sensitive => false
-
-
1
before_validation do
-
2935
name.strip!
-
end
-
-
1
before_create do
-
996
self.order_id ||= Aspect.where(user_id: user_id).maximum(:order_id || 0).to_i + 1
-
end
-
-
1
def to_s
-
53
name
-
end
-
-
1
def << (shareable)
-
87
case shareable
-
when Post
-
65
self.posts << shareable
-
when Photo
-
22
self.photos << shareable
-
else
-
raise "Unknown shareable type '#{shareable.class.base_class.to_s}'"
-
end
-
end
-
-
end
-
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class AspectMembership < ApplicationRecord
-
1
belongs_to :aspect
-
1
belongs_to :contact
-
1
has_one :user, :through => :contact
-
1
has_one :person, :through => :contact
-
-
1
before_destroy do
-
71
user&.disconnect(contact) if contact&.aspects&.size == 1
-
71
true
-
end
-
-
1
def as_json(opts={})
-
{
-
:id => self.id,
-
:person_id => self.person.id,
-
:contact_id => self.contact.id,
-
:aspect_id => self.aspect_id,
-
:aspect_ids => self.contact.aspects.map{|a| a.id}
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class AspectVisibility < ApplicationRecord
-
-
1
belongs_to :aspect
-
-
1
belongs_to :shareable, :polymorphic => true
-
-
1
validates :aspect, uniqueness: {scope: %i(shareable_id shareable_type)}
-
end
-
# frozen_string_literal: true
-
-
1
class Block < ApplicationRecord
-
1
belongs_to :person
-
1
belongs_to :user
-
-
1
delegate :name, :diaspora_handle, to: :person, prefix: true
-
-
1
validates :person_id, uniqueness: {scope: :user_id}
-
-
1
validate :not_blocking_yourself
-
-
1
def not_blocking_yourself
-
78
return unless user.person.id == person_id
-
-
1
errors.add(:person_id, "stop blocking yourself!")
-
end
-
-
# @return [Array<Person>] The recipient of the block
-
1
def subscribers
-
8
[person]
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Comment < ApplicationRecord
-
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
1
include Diaspora::Relayable
-
-
1
include Diaspora::Taggable
-
1
include Diaspora::Likeable
-
1
include Diaspora::MentionsContainer
-
1
include Reference::Source
-
-
1
acts_as_taggable_on :tags
-
1
extract_tags_from :text
-
1
before_create :build_tags
-
-
1
belongs_to :commentable, :touch => true, :polymorphic => true
-
1
alias_attribute :post, :commentable
-
1
alias_attribute :parent, :commentable
-
-
1
delegate :name, to: :author, prefix: true
-
1
delegate :comment_email_subject, to: :parent
-
1
delegate :author_name, to: :parent, prefix: true
-
-
1
validates :text, :presence => true, :length => {:maximum => 65535}
-
-
1
has_many :reports, as: :item
-
-
1
has_one :signature, class_name: "CommentSignature", dependent: :delete
-
-
33
scope :including_author, -> { includes(:author => :profile) }
-
33
scope :for_a_stream, -> { including_author.merge(order('created_at ASC')) }
-
-
1
scope :all_public, -> {
-
3
where("commentable_type = 'Post' AND EXISTS(
-
SELECT 1 FROM posts WHERE posts.id = commentable_id AND posts.public = true
-
)")
-
}
-
-
1
before_save do
-
674
self.text.strip! unless self.text.nil?
-
end
-
-
1
after_commit on: :create do
-
674
parent.update_comments_counter
-
674
parent.touch(:interacted_at) if parent.respond_to?(:interacted_at)
-
end
-
-
1
after_destroy do
-
23
self.parent.update_comments_counter
-
23
participation = author.participations.find_by(target_id: post.id)
-
23
participation.unparticipate! if participation.present?
-
end
-
-
1
def text= text
-
741
self[:text] = text.to_s.strip #to_s if for nil, for whatever reason
-
end
-
-
1
def add_mention_subscribers?
-
55
super && parent.author.local?
-
end
-
-
1
class Generator < Diaspora::Federated::Generator
-
1
def self.federated_class
-
455
Comment
-
end
-
-
1
def initialize(person, target, text)
-
455
@text = text
-
455
super(person, target)
-
end
-
-
1
def relayable_options
-
455
{post: @target, text: @text}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class CommentSignature < ApplicationRecord
-
1
include Diaspora::Signature
-
-
1
self.primary_key = :comment_id
-
1
belongs_to :comment
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Contact < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
-
1
belongs_to :user
-
1
belongs_to :person
-
-
1
validates :person_id, uniqueness: {scope: :user_id}
-
-
1
delegate :name, :diaspora_handle, :guid, :first_name,
-
to: :person, prefix: true
-
-
1
has_many :aspect_memberships, dependent: :destroy
-
1
has_many :aspects, through: :aspect_memberships
-
-
1
validate :not_contact_for_self,
-
:not_blocked_user,
-
:not_contact_with_closed_account
-
-
1
before_destroy :destroy_notifications
-
-
25
scope :all_contacts_of_person, ->(x) { where(person_id: x.id) }
-
-
# contact.sharing is true when contact.person is sharing with contact.user
-
220
scope :sharing, -> { where(sharing: true) }
-
-
# contact.receiving is true when contact.user is sharing with contact.person
-
336
scope :receiving, -> { where(receiving: true) }
-
-
100
scope :mutual, -> { sharing.receiving }
-
-
2
scope :for_a_stream, -> { includes(:aspects, person: :profile).order("profiles.last_name ASC") }
-
-
14
scope :only_sharing, -> { sharing.where(receiving: false) }
-
-
1
def destroy_notifications
-
96
Notification.where(
-
target_type: "Person",
-
target_id: person_id,
-
recipient_id: user_id,
-
type: "Notifications::StartedSharing"
-
).destroy_all
-
end
-
-
1
def mutual?
-
69
sharing && receiving
-
end
-
-
1
def in_aspect?(aspect)
-
2
if aspect_memberships.loaded?
-
aspect_memberships.detect{ |am| am.aspect_id == aspect.id }
-
2
elsif aspects.loaded?
-
aspects.detect{ |a| a.id == aspect.id }
-
else
-
2
AspectMembership.exists?(:contact_id => self.id, :aspect_id => aspect.id)
-
end
-
end
-
-
# Follows back if user setting is set so
-
1
def receive(_recipient_user_ids)
-
514
user.share_with(person, user.auto_follow_back_aspect) if user.auto_follow_back && !receiving
-
end
-
-
# object for local recipients
-
1
def object_to_receive
-
511
Contact.create_or_update_sharing_contact(person.owner, user.person)
-
end
-
-
# @return [Array<Person>] The recipient of the contact
-
1
def subscribers
-
591
[person]
-
end
-
-
# creates or updates a contact with active sharing flag. Returns nil if already sharing.
-
1
def self.create_or_update_sharing_contact(recipient, sender)
-
515
contact = recipient.contacts.find_or_initialize_by(person_id: sender.id)
-
-
515
return if contact.sharing
-
-
514
contact.update(sharing: true)
-
514
contact
-
end
-
-
1
private
-
-
1
def not_contact_with_closed_account
-
2236
errors.add(:base, "Cannot be in contact with a closed account") if person_id && person.closed_account?
-
end
-
-
1
def not_contact_for_self
-
2236
errors.add(:base, "Cannot create self-contact") if person_id && person.owner == user
-
end
-
-
1
def not_blocked_user
-
2236
if receiving && user && user.blocks.where(person_id: person_id).exists?
-
2
errors.add(:base, "Cannot connect to an ignored user")
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Conversation < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
-
1
has_many :conversation_visibilities, dependent: :destroy
-
1
has_many :participants, class_name: "Person", through: :conversation_visibilities, source: :person
-
509
has_many :messages, -> { order("created_at ASC") }, inverse_of: :conversation
-
-
1
validate :local_recipients
-
-
1
def local_recipients
-
267
recipients.each do |recipient|
-
240
if recipient.local?
-
209
unless recipient.owner.contacts.where(person_id: author.id).any? ||
-
7
(author.owner && author.owner.podmin_account?)
-
4
errors.add(:all_recipients, "recipient not allowed")
-
end
-
end
-
end
-
end
-
-
1
accepts_nested_attributes_for :messages
-
-
1
def recipients
-
270
self.participants - [self.author]
-
end
-
-
1
def first_unread_message(user)
-
8
if visibility = self.conversation_visibilities.where(:person_id => user.person.id).where('unread > 0').first
-
2
self.messages.to_a[-visibility.unread]
-
end
-
end
-
-
1
def set_read(user)
-
7
update_read_for(user, read: true)
-
end
-
-
1
def update_read_for(user, read:)
-
9
visibility = conversation_visibilities.find_by(person_id: user.person.id)
-
9
return unless visibility
-
9
visibility.unread = read ? 0 : 1
-
9
visibility.save
-
end
-
-
1
def participant_handles
-
3
participants.map(&:diaspora_handle).join(";")
-
end
-
-
1
def participant_handles=(handles)
-
6
handles.split(";").each do |handle|
-
12
participants << Person.find_or_fetch_by_identifier(handle)
-
end
-
end
-
-
1
def last_author
-
33
return unless @last_author.present? || messages.size > 0
-
33
@last_author_id ||= messages.pluck(:author_id).last
-
33
@last_author ||= Person.includes(:profile).find_by(id: @last_author_id)
-
end
-
-
1
def ordered_participants
-
18
@ordered_participants ||= (messages.map(&:author).reverse + participants).uniq
-
end
-
-
1
def subject
-
111
self[:subject].blank? ? I18n.t("conversations.new.subject_default") : self[:subject]
-
end
-
-
1
def subscribers
-
3
recipients
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ConversationVisibility < ApplicationRecord
-
-
1
belongs_to :conversation
-
1
belongs_to :person
-
-
1
after_destroy :check_orphan_conversation
-
-
1
private
-
-
1
def check_orphan_conversation
-
23
conversation = Conversation.find_by_id(self.conversation.id)
-
23
if conversation
-
23
conversation.destroy if conversation.participants.count == 0
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class InvitationCode < ApplicationRecord
-
1
belongs_to :user
-
-
1
before_create :generate_token, :set_default_invite_count
-
-
1
delegate :name, to: :user, prefix: true
-
-
1
def to_param
-
50
token
-
end
-
-
1
def can_be_used?
-
7
count > 0 && AppConfig.settings.invitations.open?
-
end
-
-
1
def add_invites!
-
update(count: count + 100)
-
end
-
-
1
def use!
-
2
update(count: count - 1)
-
end
-
-
1
def generate_token
-
63
loop do
-
63
self.token = SecureRandom.hex(6)
-
63
break unless InvitationCode.default_scoped.exists?(token: token)
-
end
-
end
-
-
1
def self.default_inviter_or(user)
-
6
if AppConfig.admins.account.present?
-
1
inviter = User.find_by_username(AppConfig.admins.account.get)
-
end
-
6
inviter ||= user
-
6
inviter
-
end
-
-
1
def set_default_invite_count
-
63
self.count = AppConfig['settings.invitations.count'] || 25
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Like < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
1
include Diaspora::Fields::Target
-
-
1
include Diaspora::Relayable
-
-
1
has_one :signature, class_name: "LikeSignature", dependent: :delete
-
-
1
alias_attribute :parent, :target
-
-
1
class Generator < Diaspora::Federated::Generator
-
1
def self.federated_class
-
300
Like
-
end
-
-
1
def relayable_options
-
300
{:target => @target, :positive => true}
-
end
-
end
-
-
1
after_commit :on => :create do
-
410
self.parent.update_likes_counter
-
end
-
-
1
after_destroy do
-
21
self.parent.update_likes_counter
-
21
participation_target_id = parent.is_a?(Comment) ? parent.commentable.id : parent.id
-
21
participation = author.participations.find_by(target_id: participation_target_id)
-
21
participation.unparticipate! if participation.present?
-
end
-
-
# NOTE API V1 to be extracted
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :id
-
1
t.add :guid
-
1
t.add :author
-
1
t.add :created_at
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class LikeSignature < ApplicationRecord
-
1
include Diaspora::Signature
-
-
1
self.primary_key = :like_id
-
1
belongs_to :like
-
end
-
# frozen_string_literal: true
-
-
1
class Location < ApplicationRecord
-
1
before_validation :split_coords, on: :create
-
1
validates_presence_of :lat, :lng
-
-
1
attr_accessor :coordinates
-
-
1
include Diaspora::Federated::Base
-
-
1
belongs_to :status_message
-
-
1
def split_coords
-
20
self.lat, self.lng = coordinates.split(',') if coordinates.present?
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Mention < ApplicationRecord
-
belongs_to :mentions_container, polymorphic: true
-
belongs_to :person
-
-
scope :local, -> {
-
joins(:person).where.not(people: {owner_id: nil})
-
}
-
-
after_destroy :delete_notification
-
-
def delete_notification
-
Notification.where(target_type: self.class.name, target_id: id).destroy_all
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Message < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
-
1
include Reference::Source
-
-
1
belongs_to :conversation, touch: true
-
-
1
delegate :name, to: :author, prefix: true
-
-
1
validates :text, presence: true
-
1
validate :participant_of_parent_conversation
-
-
1
def conversation_guid=(guid)
-
16
self.conversation_id = Conversation.where(guid: guid).ids.first
-
end
-
-
1
def increase_unread(user)
-
13
vis = ConversationVisibility.find_by(conversation_id: conversation_id, person_id: user.person.id)
-
13
return unless vis
-
13
vis.unread += 1
-
13
vis.save
-
end
-
-
1
def message
-
24
@message ||= Diaspora::MessageRenderer.new text
-
end
-
-
# @return [Array<Person>]
-
1
def subscribers
-
2
conversation.participants
-
end
-
-
1
private
-
-
1
def participant_of_parent_conversation
-
261
return unless conversation&.participants&.exclude?(author)
-
-
3
errors.add(:base, "Author is not participating in the conversation")
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
class Notification < ApplicationRecord
-
include Diaspora::Fields::Guid
-
-
belongs_to :recipient, class_name: "User"
-
has_many :notification_actors, dependent: :delete_all
-
has_many :actors, class_name: "Person", through: :notification_actors, source: :person
-
belongs_to :target, polymorphic: true
-
-
def self.for(recipient, opts={})
-
where(opts.merge!(recipient_id: recipient.id)).order("updated_at DESC")
-
end
-
-
def email_the_user(target, actor)
-
recipient.mail(mail_job, recipient_id, actor.id, target.id)
-
end
-
-
def set_read_state( read_state )
-
update_column(:unread, !read_state)
-
end
-
-
def mail_job
-
raise NotImplementedError.new("Subclass this.")
-
end
-
-
def linked_object
-
target
-
end
-
-
def self.concatenate_or_create(recipient, target, actor)
-
return nil if suppress_notification?(recipient, actor)
-
-
find_or_initialize_by(recipient: recipient, target: target, unread: true).tap do |notification|
-
notification.actors |= [actor]
-
# Explicitly touch the notification to update updated_at whenever new actor is inserted in notification.
-
if notification.new_record? || notification.changed?
-
notification.save!
-
else
-
notification.touch
-
end
-
end
-
end
-
-
def self.create_notification(recipient, target, actor)
-
return nil if suppress_notification?(recipient, actor)
-
-
create(recipient: recipient, target: target, actors: [actor])
-
end
-
-
private_class_method def self.suppress_notification?(recipient, actor)
-
recipient.blocks.where(person: actor).exists?
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class NotificationActor < ApplicationRecord
-
1
belongs_to :notification
-
1
belongs_to :person
-
end
-
# frozen_string_literal: true
-
-
module Notifications
-
class AlsoCommented < Notification
-
include Notifications::Commented
-
-
def mail_job
-
Workers::Mail::AlsoCommented
-
end
-
-
def popup_translation_key
-
"notifications.also_commented"
-
end
-
-
def self.notify(comment, _recipient_user_ids)
-
actor = comment.author
-
commentable = comment.commentable
-
recipient_ids = commentable.participants.local.where.not(id: [commentable.author_id, actor.id]).pluck(:owner_id)
-
-
User.where(id: recipient_ids).find_each do |recipient|
-
next if recipient.is_shareable_hidden?(commentable) || mention_notification_exists?(comment, recipient.person)
-
-
concatenate_or_create(recipient, commentable, actor).try(:email_the_user, comment, actor)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class CommentOnPost < Notification
-
1
include Notifications::Commented
-
-
1
def mail_job
-
10
Workers::Mail::CommentOnPost
-
end
-
-
1
def popup_translation_key
-
"notifications.comment_on_post"
-
end
-
-
1
def self.notify(comment, _recipient_user_ids)
-
23
actor = comment.author
-
23
commentable_author = comment.commentable.author
-
-
23
return unless commentable_author.local? && actor != commentable_author
-
16
return if mention_notification_exists?(comment, commentable_author)
-
-
10
concatenate_or_create(commentable_author.owner, comment.commentable, actor).email_the_user(comment, actor)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Notifications
-
module Commented
-
extend ActiveSupport::Concern
-
-
def deleted_translation_key
-
"notifications.also_commented_deleted"
-
end
-
-
module ClassMethods
-
def mention_notification_exists?(comment, recipient_person)
-
Notifications::MentionedInComment.exists?(target: comment.mentions.where(person: recipient_person))
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class ContactsBirthday < Notification
-
1
def mail_job
-
2
Workers::Mail::ContactsBirthday
-
end
-
-
1
def popup_translation_key
-
2
"notifications.contacts_birthday"
-
end
-
-
1
def self.notify(contact, _recipient_user_ids)
-
3
recipient = contact.user
-
3
actor = contact.person
-
3
create_notification(recipient, actor, actor).try(:email_the_user, actor, actor)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class Liked < Notification
-
1
def mail_job
-
35
Workers::Mail::Liked
-
end
-
-
1
def popup_translation_key
-
5
"notifications.liked"
-
end
-
-
1
def deleted_translation_key
-
2
"notifications.liked_post_deleted"
-
end
-
-
1
def self.notify(like, _recipient_user_ids)
-
35
actor = like.author
-
35
target_author = like.target.author
-
-
35
return unless like.target_type == "Post" && target_author.local? && actor != target_author
-
-
35
concatenate_or_create(target_author.owner, like.target, actor).email_the_user(like, actor)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class LikedComment < Notification
-
1
def mail_job
-
Workers::Mail::LikedComment
-
end
-
-
1
def popup_translation_key
-
"notifications.liked_comment"
-
end
-
-
1
def deleted_translation_key
-
"notifications.liked_comment_deleted"
-
end
-
-
1
def self.notify(like, _recipient_user_ids)
-
3
actor = like.author
-
3
target_author = like.target.author
-
-
3
return unless like.target_type == "Comment" && target_author.local? && actor != target_author
-
-
concatenate_or_create(target_author.owner, like.target, actor).email_the_user(like, actor)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
module Mentioned
-
1
extend ActiveSupport::Concern
-
-
1
def linked_object
-
87
target.mentions_container
-
end
-
-
1
module ClassMethods
-
1
def notify(mentionable, recipient_user_ids)
-
1015
actor = mentionable.author
-
1015
relevant_mentions = filter_mentions(
-
mentionable.mentions.local.where.not(person: actor),
-
mentionable,
-
recipient_user_ids
-
)
-
-
1015
relevant_mentions.each do |mention|
-
138
recipient = mention.person.owner
-
138
unless exists?(recipient: recipient, target: mention)
-
137
create_notification(recipient, mention, actor).try(:email_the_user, mention, actor)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class MentionedInComment < Notification
-
1
include Notifications::Mentioned
-
-
1
def popup_translation_key
-
1
"notifications.mentioned_in_comment"
-
end
-
-
1
def deleted_translation_key
-
"notifications.mentioned_in_comment_deleted"
-
end
-
-
1
def self.filter_mentions(mentions, mentionable, _recipient_user_ids)
-
25
mentions.includes(:person).merge(Person.allowed_to_be_mentioned_in_a_comment_to(mentionable.parent))
-
end
-
-
1
def mail_job
-
20
if !recipient.user_preferences.exists?(email_type: "mentioned_in_comment")
-
18
Workers::Mail::MentionedInComment
-
2
elsif shareable.author.owner_id == recipient_id
-
1
Workers::Mail::CommentOnPost
-
1
elsif shareable.participants.local.where(owner_id: recipient_id)
-
1
Workers::Mail::AlsoCommented
-
end
-
end
-
-
1
private
-
-
1
def shareable
-
3
linked_object.parent
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class MentionedInPost < Notification
-
1
include Notifications::Mentioned
-
-
1
def mail_job
-
113
Workers::Mail::Mentioned
-
end
-
-
1
def popup_translation_key
-
1
"notifications.mentioned"
-
end
-
-
1
def deleted_translation_key
-
"notifications.mentioned_deleted"
-
end
-
-
1
def self.filter_mentions(mentions, mentionable, recipient_user_ids)
-
985
return mentions if mentionable.public
-
583
mentions.where(person: Person.where(owner_id: recipient_user_ids).ids)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class PrivateMessage < Notification
-
1
def mail_job
-
5
Workers::Mail::PrivateMessage
-
end
-
-
1
def popup_translation_key
-
"notifications.private_message"
-
end
-
-
1
def self.notify(object, _recipient_user_ids)
-
6
case object
-
when Conversation
-
3
object.messages.each {|message| notify_message(message) }
-
when Message
-
4
notify_message(object)
-
end
-
end
-
-
1
private_class_method def self.notify_message(message)
-
5
recipient_ids = message.conversation.participants.local.where.not(id: message.author_id).pluck(:owner_id)
-
5
User.where(id: recipient_ids).find_each do |recipient|
-
5
message.increase_unread(recipient)
-
5
new(recipient: recipient).email_the_user(message, message.author)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class Reshared < Notification
-
1
def mail_job
-
4
Workers::Mail::Reshared
-
end
-
-
1
def popup_translation_key
-
"notifications.reshared"
-
end
-
-
1
def deleted_translation_key
-
"notifications.reshared_post_deleted"
-
end
-
-
1
def self.notify(reshare, _recipient_user_ids)
-
20
return unless reshare.root.present? && reshare.root.author.local?
-
-
5
actor = reshare.author
-
5
concatenate_or_create(reshare.root.author.owner, reshare.root, actor).try(:email_the_user, reshare, actor)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Notifications
-
1
class StartedSharing < Notification
-
1
def mail_job
-
514
Workers::Mail::StartedSharing
-
end
-
-
1
def popup_translation_key
-
7
"notifications.started_sharing"
-
end
-
-
1
def self.notify(contact, _recipient_user_ids)
-
515
sender = contact.person
-
515
create_notification(contact.user, sender, sender).try(:email_the_user, sender, sender)
-
end
-
-
1
def contact
-
3
recipient.contact_for(target)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class OEmbedCache < ApplicationRecord
-
1
serialize :data
-
1
validates :data, :presence => true
-
-
1
has_many :posts
-
-
# NOTE API V1 to be extracted
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :data
-
end
-
-
1
def self.find_or_create_by(opts)
-
7
cache = OEmbedCache.find_or_initialize_by(opts)
-
7
return cache if cache.persisted?
-
5
cache.fetch_and_save_oembed_data! # make after create callback and drop this method ?
-
5
cache
-
end
-
-
1
def fetch_and_save_oembed_data!
-
begin
-
5
response = OEmbed::Providers.get(self.url, {:maxwidth => 420, :maxheight => 420, :frame => 1, :iframe => 1})
-
rescue => e
-
# noop
-
else
-
4
self.data = response.fields
-
4
self.data['trusted_endpoint_url'] = response.provider.endpoint
-
4
self.save
-
end
-
end
-
-
1
def is_trusted_and_has_html?
-
4
self.from_trusted? and self.data.has_key?('html')
-
end
-
-
1
def from_trusted?
-
4
SECURE_ENDPOINTS.include?(self.data['trusted_endpoint_url'])
-
end
-
-
1
def options_hash(prefix = 'thumbnail_')
-
2
return nil unless self.data.has_key?(prefix + 'url')
-
{
-
2
:height => self.data.fetch(prefix + 'height', ''),
-
:width => self.data.fetch(prefix + 'width', ''),
-
:alt => self.data.fetch('title', ''),
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class OpenGraphCache < ApplicationRecord
-
1
validates :title, :presence => true
-
1
validates :ob_type, :presence => true
-
1
validates :image, :presence => true
-
1
validates :url, :presence => true
-
-
1
has_many :posts
-
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :title
-
1
t.add :ob_type
-
1
t.add :image
-
1
t.add :description
-
1
t.add :url
-
1
t.add :video_url
-
end
-
-
1
def image
-
20
if AppConfig.privacy.camo.proxy_opengraph_thumbnails?
-
Diaspora::Camo.image_url(self[:image])
-
else
-
20
self[:image]
-
end
-
end
-
-
1
def self.find_or_create_by(opts)
-
7
cache = OpenGraphCache.find_or_initialize_by(opts)
-
7
cache.fetch_and_save_opengraph_data! unless cache.persisted?
-
7
cache if cache.persisted? # Make this an after create callback and drop this method ?
-
end
-
-
1
def fetch_and_save_opengraph_data!
-
8
uri = URI.parse(url.start_with?("http") ? url : "http://#{url}")
-
8
uri.normalize!
-
8
object = OpenGraphReader.fetch!(uri)
-
-
6
return unless object
-
-
6
self.title = object.og.title.truncate(255)
-
6
self.ob_type = object.og.type
-
6
self.image = object.og.image.url
-
6
self.url = object.og.url
-
6
self.description = object.og.description
-
6
if object.og.video.try(:secure_url) && secure_video_url?(object.og.video.secure_url)
-
1
self.video_url = object.og.video.secure_url
-
end
-
-
6
self.save
-
rescue OpenGraphReader::NoOpenGraphDataError, OpenGraphReader::InvalidObjectError
-
end
-
-
1
def secure_video_url?(url)
-
4
SECURE_OPENGRAPH_VIDEO_URLS.any? {|u| u =~ url }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Participation < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
1
include Diaspora::Fields::Target
-
-
1
class Generator < Diaspora::Federated::Generator
-
1
def self.federated_class
-
317
Participation
-
end
-
-
1
def relayable_options
-
317
{:target => @target}
-
end
-
end
-
-
1
def unparticipate!
-
20
if count == 1
-
13
destroy
-
else
-
7
update!(count: count.pred)
-
end
-
end
-
-
# @return [Array<Person>]
-
1
def subscribers
-
12
[target.author]
-
end
-
-
# NOTE API V1 to be extracted
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :id
-
1
t.add :guid
-
1
t.add :author
-
1
t.add :created_at
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Person < ApplicationRecord
-
1
include Diaspora::Fields::Guid
-
-
# NOTE API V1 to be extracted
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :id
-
1
t.add :guid
-
1
t.add :name
-
1
t.add lambda { |person|
-
361
person.diaspora_handle
-
}, :as => :diaspora_id
-
1
t.add lambda { |person|
-
361
{small: person.profile.image_url(size: :thumb_small),
-
medium: person.profile.image_url(size: :thumb_medium),
-
large: person.profile.image_url(size: :thumb_large)}
-
}, as: :avatar
-
end
-
-
1
has_one :profile, dependent: :destroy
-
1
delegate :last_name, :full_name, :image_url, :tag_string, :bio, :location,
-
:gender, :birthday, :formatted_birthday, :tags, :searchable,
-
:public_details?, to: :profile
-
1
accepts_nested_attributes_for :profile
-
-
1
before_validation :downcase_diaspora_handle
-
-
1
def downcase_diaspora_handle
-
11295
diaspora_handle.downcase! unless diaspora_handle.blank?
-
end
-
-
1
has_many :contacts, :dependent => :destroy # Other people's contacts for this person
-
1
has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
-
1
has_many :photos, :foreign_key => :author_id, :dependent => :destroy # This person's own photos
-
1
has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments
-
1
has_many :likes, foreign_key: :author_id, dependent: :destroy # This person's own likes
-
1
has_many :participations, :foreign_key => :author_id, :dependent => :destroy
-
1
has_many :poll_participations, foreign_key: :author_id, dependent: :destroy
-
1
has_many :conversation_visibilities, dependent: :destroy
-
1
has_many :messages, foreign_key: :author_id, dependent: :destroy
-
1
has_many :conversations, foreign_key: :author_id, dependent: :destroy
-
1
has_many :blocks, dependent: :destroy
-
-
1
has_many :roles
-
-
1
belongs_to :owner, class_name: "User", optional: true
-
1
belongs_to :pod, optional: true
-
-
1
has_many :notification_actors
-
1
has_many :notifications, :through => :notification_actors
-
-
1
has_many :mentions, :dependent => :destroy
-
-
1
has_one :account_deletion, dependent: :destroy
-
1
has_one :account_migration, foreign_key: :old_person_id, dependent: :nullify, inverse_of: :old_person
-
-
1
validate :owner_xor_pod
-
1
validate :other_person_with_same_guid, on: :create
-
1
validates :profile, :presence => true
-
1
validates :serialized_public_key, :presence => true
-
1
validates :diaspora_handle, :uniqueness => true
-
-
1
scope :searchable, -> (user) {
-
48
if user
-
45
joins("LEFT OUTER JOIN contacts ON contacts.user_id = #{user.id} AND contacts.person_id = people.id")
-
.joins(:profile)
-
.where("profiles.searchable = true OR contacts.user_id = ?", user.id)
-
else
-
3
joins(:profile).where(profiles: {searchable: true})
-
end
-
}
-
-
55
scope :remote, -> { where('people.owner_id IS NULL') }
-
39
scope :local, -> { where('people.owner_id IS NOT NULL') }
-
17
scope :for_json, -> { select("people.id, people.guid, people.diaspora_handle").includes(:profile) }
-
-
# @note user is passed in here defensively
-
1
scope :all_from_aspects, ->(aspect_ids, user) {
-
8
joins(:contacts => :aspect_memberships).
-
where(:contacts => {:user_id => user.id}).
-
where(:aspect_memberships => {:aspect_id => aspect_ids})
-
}
-
-
1
scope :unique_from_aspects, ->(aspect_ids, user) {
-
1
all_from_aspects(aspect_ids, user).select('DISTINCT people.*')
-
}
-
-
#not defensive
-
1
scope :in_aspects, ->(aspect_ids) {
-
1148
joins(contacts: :aspect_memberships)
-
.where(aspect_memberships: {aspect_id: aspect_ids}).distinct
-
}
-
-
1
scope :in_all_aspects, ->(aspect_ids) {
-
joins(contacts: :aspect_memberships)
-
.where(aspect_memberships: {aspect_id: aspect_ids})
-
}
-
-
1
scope :contacts_of, ->(user) {
-
17
joins(:contacts).where(contacts: {user_id: user.id})
-
}
-
-
1
scope :profile_tagged_with, ->(tag_name) {
-
16
joins(:profile => :tags)
-
.where(:tags => {:name => tag_name})
-
.where('profiles.searchable IS TRUE')
-
}
-
-
1
scope :who_have_reshared_a_users_posts, ->(user) {
-
5
joins(:posts)
-
.where(:posts => {:root_guid => StatusMessage.guids_for_author(user.person), :type => 'Reshare'} )
-
}
-
-
# This scope selects people where the full name contains the search_str or diaspora ID
-
# starts with the search_str.
-
# However, if the search_str doesn't have more than 1 non-whitespace character, it'll return an empty set.
-
# @param [String] search substring
-
# @return [Person::ActiveRecord_Relation]
-
1
scope :find_by_substring, ->(search_str) {
-
87
search_str = search_str.strip
-
87
if search_str.blank? || search_str.size < 2
-
11
none
-
else
-
76
sql, tokens = search_query_string(search_str)
-
76
joins(:profile).where(sql, *tokens)
-
end
-
}
-
-
# Left joins likes and comments to a specific post where people are authors of these comments and likes
-
# @param [String, Integer] post ID for which comments and likes should be joined
-
# @return [Person::ActiveRecord_Relation]
-
1
scope :left_join_visible_post_interactions_on_authorship, ->(post_id) {
-
38
comments_sql = <<-SQL
-
LEFT OUTER JOIN comments ON
-
comments.author_id = people.id AND comments.commentable_type = 'Post' AND comments.commentable_id = #{post_id}
-
SQL
-
-
38
likes_sql = <<-SQL
-
LEFT OUTER JOIN likes ON
-
likes.author_id = people.id AND likes.target_type = 'Post' AND likes.target_id = #{post_id}
-
SQL
-
-
38
joins(comments_sql).joins(likes_sql)
-
}
-
-
# Selects people who can be mentioned in a comment to a specific post. For public posts all people
-
# are allowed, so no additional constraints are added. For private posts selection is limited to
-
# people who have posted comments or likes for this post.
-
# @param [Post] the post for which we query mentionable in comments people
-
# @return [Person::ActiveRecord_Relation]
-
1
scope :allowed_to_be_mentioned_in_a_comment_to, ->(post) {
-
42
allowed = if post.public?
-
21
all
-
else
-
21
left_join_visible_post_interactions_on_authorship(post.id)
-
.where("comments.id IS NOT NULL OR likes.id IS NOT NULL OR people.id = #{post.author_id}")
-
end
-
42
allowed.distinct
-
}
-
-
# This scope adds sorting of people in the order, appropriate for suggesting to a user (current user) who
-
# has requested a list of the people mentionable in a comment for a specific post.
-
# Sorts people in the following priority: post author > commenters > likers > contacts > non-contacts
-
# @param [Post] post for which the mentionable in comment people list is requested
-
# @param [User] user who requests the people list
-
# @return [Person::ActiveRecord_Relation]
-
1
scope :sort_for_mention_suggestion, ->(post, user) {
-
17
left_join_visible_post_interactions_on_authorship(post.id)
-
.joins("LEFT OUTER JOIN contacts ON people.id = contacts.person_id AND contacts.user_id = #{user.id}")
-
.joins(:profile)
-
17
.select(<<-SQL
-
17
people.id = #{unscoped { post.author_id }} AS is_author,
-
comments.id IS NOT NULL AS is_commenter,
-
likes.id IS NOT NULL AS is_liker,
-
contacts.id IS NOT NULL AS is_contact
-
SQL
-
)
-
17
.order(Arel.sql(<<-SQL
-
is_author DESC,
-
is_commenter DESC,
-
is_liker DESC,
-
is_contact DESC,
-
profiles.full_name,
-
people.diaspora_handle
-
SQL
-
))
-
}
-
-
1
def self.community_spotlight
-
11
Person.joins(:roles).where(:roles => {:name => 'spotlight'})
-
end
-
-
# Set a default of an empty profile when a new Person record is instantiated.
-
# Passing :profile => nil to Person.new will instantiate a person with no profile.
-
# Calling Person.new with a block:
-
# Person.new do |p|
-
# p.profile = nil
-
# end
-
# will not work! The nil profile will be overriden with an empty one.
-
1
def initialize(params={})
-
5703
params = {} if params.nil?
-
-
5703
profile_set = params.has_key?(:profile) || params.has_key?("profile")
-
5703
params[:profile_attributes] = params.delete(:profile) if params.has_key?(:profile) && params[:profile].is_a?(Hash)
-
5703
super
-
5703
self.profile ||= Profile.new unless profile_set
-
end
-
-
1
def self.find_from_guid_or_username(params)
-
97
p = if params[:id].present?
-
89
Person.find_by(guid: params[:id])
-
8
elsif params[:username].present? && u = User.find_by_username(params[:username])
-
6
u.person
-
else
-
2
nil
-
end
-
97
raise ActiveRecord::RecordNotFound unless p.present?
-
91
p
-
end
-
-
1
def to_param
-
605
self.guid
-
end
-
-
1
def self.search_query_string(query)
-
76
query = query.downcase
-
76
like_operator = AppConfig.postgres? ? "ILIKE" : "LIKE"
-
-
76
where_clause = <<-SQL
-
profiles.full_name #{like_operator} ? OR
-
people.diaspora_handle #{like_operator} ?
-
SQL
-
-
76
q_tokens = []
-
248
q_tokens[0] = query.to_s.strip.gsub(/(\s|$|^)/) { "%#{$1}" }
-
76
q_tokens[1] = q_tokens[0].gsub(/\s/,'').gsub('%','')
-
76
q_tokens[1] << "%"
-
-
76
[where_clause, q_tokens]
-
end
-
-
1
def self.search(search_str, user, only_contacts: false, mutual: false)
-
49
query = find_by_substring(search_str)
-
49
return query if query.is_a?(ActiveRecord::NullRelation)
-
-
42
query = if only_contacts
-
12
query.contacts_of(user)
-
else
-
30
query.searchable(user)
-
end
-
-
42
query = query.where(contacts: {sharing: true, receiving: true}) if mutual
-
-
42
query.where(closed_account: false)
-
.order([Arel.sql("contacts.user_id IS NULL"), "profiles.last_name ASC", "profiles.first_name ASC"])
-
end
-
-
1
def name(opts = {})
-
6556
if self.profile.nil?
-
fix_profile
-
end
-
6556
@name ||= Person.name_from_attrs(self.profile.first_name, self.profile.last_name, self.diaspora_handle)
-
end
-
-
1
def self.name_from_attrs(first_name, last_name, diaspora_handle)
-
3361
first_name.blank? && last_name.blank? ? diaspora_handle : "#{first_name.to_s.strip} #{last_name.to_s.strip}".strip
-
end
-
-
1
def first_name
-
1438
@first_name ||= if profile.nil? || profile.first_name.nil? || profile.first_name.blank?
-
2
self.diaspora_handle.split('@').first
-
else
-
754
names = profile.first_name.to_s.split(/\s/)
-
754
str = names[0...-1].join(' ')
-
754
str = names[0] if str.blank?
-
754
str
-
end
-
end
-
-
1
def username
-
1579
@username ||= owner ? owner.username : diaspora_handle.split("@")[0]
-
end
-
-
1
def author
-
2
self
-
end
-
-
1
def owns?(obj)
-
36
self.id == obj.author_id
-
end
-
-
1
def url
-
23
url_to "/"
-
end
-
-
1
def profile_url
-
12
url_to "/u/#{username}"
-
end
-
-
1
def atom_url
-
498
url_to "/public/#{username}.atom"
-
end
-
-
1
def receive_url
-
151
url_to "/receive/users/#{guid}"
-
end
-
-
# @param path [String]
-
# @return [String]
-
1
def url_to(path)
-
763
local? ? AppConfig.url_to(path) : pod.url_to(path)
-
end
-
-
1
def public_key_hash
-
Base64.encode64(OpenSSL::Digest::SHA256.new(serialized_public_key).to_s)
-
end
-
-
1
def public_key
-
266
OpenSSL::PKey::RSA.new(serialized_public_key)
-
rescue OpenSSL::PKey::RSAError
-
1
nil
-
end
-
-
1
def exported_key
-
serialized_public_key
-
end
-
-
# discovery (webfinger)
-
1
def self.find_or_fetch_by_identifier(diaspora_id)
-
# exiting person?
-
791
person = by_account_identifier(diaspora_id)
-
791
return person if person.present? && person.profile.present?
-
-
# create or update person from webfinger
-
11
logger.info "webfingering #{diaspora_id}, it is not known or needs updating"
-
11
DiasporaFederation::Discovery::Discovery.new(diaspora_id).fetch_and_save
-
-
5
by_account_identifier(diaspora_id)
-
end
-
-
1
def self.by_account_identifier(diaspora_id)
-
1568
find_by(diaspora_handle: diaspora_id.strip.downcase)
-
end
-
-
1
def remote?
-
9610
owner_id.nil?
-
end
-
1
def local?
-
8797
!remote?
-
end
-
-
1
def has_photos?
-
2
self.photos.exists?
-
end
-
-
1
def as_json( opts = {} )
-
14
opts ||= {}
-
json = {
-
14
id: id,
-
guid: guid,
-
name: name,
-
avatar: profile.image_url(size: :thumb_small),
-
handle: diaspora_handle,
-
url: Rails.application.routes.url_helpers.person_path(self)
-
}
-
16
json.merge!(:tags => self.profile.tags.map{|t| "##{t.name}"}) if opts[:includes] == "tags"
-
14
json
-
end
-
-
1
def lock_access!
-
141
self.closed_account = true
-
141
self.save
-
end
-
-
1
def clear_profile!
-
126
self.profile.tombstone!
-
126
self
-
end
-
-
1
private
-
-
1
def fix_profile
-
logger.info "fix profile for account: #{diaspora_handle}"
-
DiasporaFederation::Discovery::Discovery.new(diaspora_handle).fetch_and_save
-
reload
-
end
-
-
1
def owner_xor_pod
-
11295
errors.add(:base, "Specify an owner or a pod, not both") unless owner.blank? ^ pod.blank?
-
end
-
-
1
def other_person_with_same_guid
-
7113
diaspora_id = Person.where(guid: guid).where.not(diaspora_handle: diaspora_handle).pluck(:diaspora_handle).first
-
7113
errors.add(:base, "Person with same GUID already exists: #{diaspora_id}") if diaspora_id
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2009, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Photo < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Shareable
-
-
# NOTE API V1 to be extracted
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :id
-
1
t.add :guid
-
1
t.add :created_at
-
1
t.add :author
-
1
t.add lambda { |photo|
-
{
-
2
small: photo.url(:thumb_small),
-
medium: photo.url(:thumb_medium),
-
large: photo.url(:scaled_full),
-
raw: photo.url
-
}
-
}, as: :sizes
-
1
t.add lambda { |photo|
-
{
-
2
height: photo.height,
-
width: photo.width
-
}
-
}, as: :dimensions
-
1
t.add lambda { |photo|
-
{
-
id: photo.status_message.id
-
2
} if photo.status_message
-
}, as: :status_message
-
end
-
-
1
mount_uploader :processed_image, ProcessedImage
-
1
mount_uploader :unprocessed_image, UnprocessedImage
-
1
attr_accessor :keep_original_format
-
-
1
belongs_to :status_message, foreign_key: :status_message_guid, primary_key: :guid, optional: true
-
1
validates_associated :status_message
-
1
delegate :author_name, to: :status_message, prefix: true
-
-
1
validate :ownership_of_status_message
-
-
1
before_destroy :ensure_user_picture
-
1
after_destroy :clear_empty_status_message
-
-
1
after_commit on: :create do
-
657
queue_processing_job if author.local?
-
end
-
-
1
scope :on_statuses, ->(post_guids) {
-
where(status_message_guid: post_guids)
-
}
-
-
1
def clear_empty_status_message
-
17
if status_message&.text_and_photos_blank?
-
2
status_message.destroy
-
else
-
15
true
-
end
-
end
-
-
1
def ownership_of_status_message
-
1809
message = StatusMessage.find_by(guid: status_message_guid)
-
1809
return unless status_message_guid && message && diaspora_handle != message.diaspora_handle
-
-
errors.add(:base, "Photo must have the same owner as status message")
-
end
-
-
1
def self.diaspora_initialize(params={})
-
617
photo = new(params.to_hash.stringify_keys.slice(*column_names, "author"))
-
617
photo.random_string = SecureRandom.hex(10)
-
-
617
if params[:user_file]
-
616
image_file = params.delete(:user_file)
-
616
photo.unprocessed_image.store! image_file
-
1
elsif params[:image_url]
-
1
photo.remote_unprocessed_image_url = params[:image_url]
-
1
photo.unprocessed_image.store!
-
end
-
-
613
photo.update_remote_path
-
-
613
photo
-
end
-
-
1
def processed?
-
487
processed_image.path.present?
-
end
-
-
1
def update_remote_path
-
682
remote_path = if unprocessed_image.url.match(%r{^https?://})
-
unprocessed_image.url
-
else
-
682
"#{AppConfig.pod_uri.to_s.chomp('/')}#{unprocessed_image.url}"
-
end
-
-
682
name_start = remote_path.rindex "/"
-
682
self.remote_photo_path = "#{remote_path.slice(0, name_start)}/"
-
682
self.remote_photo_name = remote_path.slice(name_start + 1, remote_path.length)
-
end
-
-
1
def url(name=nil)
-
199
if remote_photo_path.present? && remote_photo_name.present?
-
199
name = "#{name}_" if name
-
199
image_url = remote_photo_path + name.to_s + remote_photo_name
-
199
camo_image_url(image_url)
-
elsif processed?
-
processed_image.url(name)
-
else
-
unprocessed_image.url(name)
-
end
-
end
-
-
1
def ensure_user_picture
-
17
profiles = Profile.where(image_url: url(:thumb_large))
-
17
profiles.each { |profile|
-
2
profile.image_url = nil
-
2
profile.save
-
}
-
end
-
-
1
def queue_processing_job
-
607
Workers::ProcessPhoto.perform_async(id)
-
end
-
-
1
def self.visible(current_user, person, limit=:all, max_time=nil)
-
57
photos = if current_user
-
41
current_user.photos_from(person, limit: limit, max_time: max_time)
-
else
-
16
Photo.where(author_id: person.id, public: true)
-
end
-
57
photos.where(pending: false).order("created_at DESC")
-
end
-
-
1
private
-
-
1
def camo_image_url(image_url)
-
199
if AppConfig.privacy.camo.proxy_remote_pod_images?
-
Diaspora::Camo.image_url(image_url)
-
else
-
199
image_url
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Pod < ApplicationRecord
-
# a pod is active if it is online or was online less than 14 days ago
-
1
ACTIVE_DAYS = 14.days
-
-
1
enum status: %i(
-
unchecked
-
no_errors
-
dns_failed
-
net_failed
-
ssl_failed
-
http_failed
-
version_failed
-
unknown_error
-
)
-
-
ERROR_MAP = {
-
1
ConnectionTester::AddressFailure => :dns_failed,
-
ConnectionTester::DNSFailure => :dns_failed,
-
ConnectionTester::NetFailure => :net_failed,
-
ConnectionTester::SSLFailure => :ssl_failed,
-
ConnectionTester::HTTPFailure => :http_failed,
-
ConnectionTester::NodeInfoFailure => :version_failed
-
}.freeze
-
-
# this are only the most common errors, the rest will be +unknown_error+
-
1
CURL_ERROR_MAP = {
-
couldnt_resolve_host: :dns_failed,
-
couldnt_connect: :net_failed,
-
operation_timedout: :net_failed,
-
ssl_cipher: :ssl_failed,
-
ssl_cacert: :ssl_failed,
-
redirected_to_other_hostname: :http_failed
-
}.freeze
-
-
# use -1 as port for default ports
-
# we can't use the real default port (80/443) because we need to handle them
-
# like both are the same and not both can exist at the same time.
-
# we also can't use nil, because databases don't handle NULL in unique indexes
-
# (except postgres >= 15 with "NULLS NOT DISTINCT").
-
1
DEFAULT_PORT = -1
-
1
DEFAULT_PORTS = [URI::HTTP::DEFAULT_PORT, URI::HTTPS::DEFAULT_PORT].freeze
-
-
1
has_many :people
-
-
1
scope :check_failed, lambda {
-
2
where(arel_table[:status].gt(Pod.statuses[:no_errors])).where.not(status: Pod.statuses[:version_failed])
-
}
-
-
1
scope :active, -> {
-
2
where(["offline_since is null or offline_since > ?", DateTime.now.utc - ACTIVE_DAYS])
-
}
-
-
1
validate :not_own_pod
-
-
1
class << self
-
1
def find_or_create_by(opts) # Rename this method to not override an AR method
-
3380
uri = URI.parse(opts.fetch(:url))
-
3380
port = DEFAULT_PORTS.include?(uri.port) ? DEFAULT_PORT : uri.port
-
3380
find_or_initialize_by(host: uri.host.downcase, port: port).tap do |pod|
-
3380
pod.ssl ||= (uri.scheme == "https")
-
3380
pod.save
-
end
-
end
-
-
# don't consider a failed version reading to be fatal
-
1
def offline_statuses
-
321
[Pod.statuses[:dns_failed],
-
Pod.statuses[:net_failed],
-
Pod.statuses[:ssl_failed],
-
Pod.statuses[:http_failed],
-
Pod.statuses[:unknown_error]]
-
end
-
-
1
def check_all!
-
4
Pod.find_in_batches(batch_size: 20) {|batch| batch.each(&:test_connection!) }
-
end
-
-
1
def check_scheduled!
-
2
Pod.where(scheduled_check: true).find_each(&:test_connection!)
-
end
-
end
-
-
1
def offline?
-
321
Pod.offline_statuses.include?(Pod.statuses[status])
-
end
-
-
# a pod is active if it is online or was online recently
-
1
def active?
-
204
!offline? || offline_since.try {|date| date > DateTime.now.utc - ACTIVE_DAYS }
-
end
-
-
1
def to_s
-
1
"#{id}:#{host}"
-
end
-
-
1
def schedule_check_if_needed
-
85
update_column(:scheduled_check, true) if offline? && !scheduled_check
-
end
-
-
1
def test_connection!
-
6
result = ConnectionTester.check uri.to_s
-
6
logger.debug "tested pod: '#{uri}' - #{result.inspect}"
-
-
6
transaction do
-
6
update_from_result(result)
-
end
-
end
-
-
# @param path [String]
-
# @return [String]
-
1
def url_to(path)
-
630
uri.tap {|uri| uri.path = path }.to_s
-
end
-
-
1
def update_offline_since
-
14
if offline?
-
9
self.offline_since ||= DateTime.now.utc
-
else
-
5
self.offline_since = nil
-
end
-
end
-
-
1
private
-
-
1
def update_from_result(result)
-
6
self.status = status_from_result(result)
-
6
update_offline_since
-
6
logger.warn "#{uri} OFFLINE: #{result.failure_message}" if offline?
-
-
6
attributes_from_result(result)
-
6
touch(:checked_at)
-
6
self.scheduled_check = false
-
-
6
save
-
end
-
-
1
def attributes_from_result(result)
-
6
self.ssl ||= result.ssl
-
6
self.error = result.error? ? result.failure_message[0..254] : nil
-
6
self.software = result.software_version[0..254] if result.software_version.present?
-
6
self.response_time = result.rt
-
end
-
-
1
def status_from_result(result)
-
6
if result.error?
-
4
ERROR_MAP.fetch(result.error.class, :unknown_error)
-
else
-
2
:no_errors
-
end
-
end
-
-
# @return [URI]
-
1
def uri
-
331
@uri ||= (ssl ? URI::HTTPS : URI::HTTP).build(host: host, port: real_port)
-
331
@uri.dup
-
end
-
-
1
def real_port
-
311
if port == DEFAULT_PORT
-
309
ssl ? URI::HTTPS::DEFAULT_PORT : URI::HTTP::DEFAULT_PORT
-
else
-
2
port
-
end
-
end
-
-
1
def not_own_pod
-
3447
pod_uri = AppConfig.pod_uri
-
3447
pod_port = DEFAULT_PORTS.include?(pod_uri.port) ? DEFAULT_PORT : pod_uri.port
-
3447
errors.add(:base, "own pod not allowed") if pod_uri.host.downcase == host && pod_port == port
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Poll < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Federated::Fetchable
-
-
1
belongs_to :status_message
-
373
has_many :poll_answers, -> { order "id ASC" }, dependent: :destroy
-
1
has_many :poll_participations, dependent: :destroy
-
1
has_one :author, through: :status_message
-
-
#forward some requests to status message, because a poll is just attached to a status message and is not sharable itself
-
1
delegate :author_id, :diaspora_handle, :public?, :subscribers, to: :status_message
-
-
1
validate :enough_poll_answers
-
1
validates :question, presence: true
-
-
3
scope :all_public, -> { joins(:status_message).where(posts: {public: true}) }
-
-
1
self.include_root_in_json = false
-
-
1
def enough_poll_answers
-
271
errors.add(:poll_answers, I18n.t("activerecord.errors.models.poll.attributes.poll_answers.not_enough_poll_answers")) if poll_answers.size < 2
-
end
-
-
1
def as_json(options={})
-
{
-
poll_id: id,
-
post_id: status_message.id,
-
question: question,
-
poll_answers: poll_answers,
-
participation_count: participation_count
-
}
-
end
-
-
1
def participation_answer(user)
-
9
poll_participations.find_by(author_id: user.person.id)
-
end
-
-
1
def participation_count
-
21
poll_answers.sum("vote_count")
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PollAnswer < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
-
1
belongs_to :poll
-
1
has_many :poll_participations
-
-
1
validates :answer, presence: true
-
-
1
self.include_root_in_json = false
-
end
-
# frozen_string_literal: true
-
-
1
class PollParticipation < ApplicationRecord
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Fields::Guid
-
1
include Diaspora::Fields::Author
-
1
include Diaspora::Relayable
-
-
1
belongs_to :poll
-
1
belongs_to :poll_answer, counter_cache: :vote_count
-
1
has_one :status_message, through: :poll
-
-
1
has_one :signature, class_name: "PollParticipationSignature", dependent: :delete
-
-
1
alias_attribute :parent, :poll
-
-
1
validate :not_already_participated
-
-
1
def poll_answer_guid=(new_poll_answer_guid)
-
20
self.poll_answer_id = PollAnswer.where(guid: new_poll_answer_guid).ids.first
-
end
-
-
1
def not_already_participated
-
80
return if poll.nil?
-
-
79
other_participations = PollParticipation.where(author_id: self.author.id, poll_id: self.poll.id).to_a-[self]
-
79
if other_participations.present?
-
4
self.errors.add(:poll, I18n.t("activerecord.errors.models.poll_participation.attributes.poll.already_participated"))
-
end
-
end
-
-
1
class Generator < Diaspora::Federated::Generator
-
1
def self.federated_class
-
31
PollParticipation
-
end
-
-
1
def initialize(person, target, poll_answer)
-
31
@poll_answer = poll_answer
-
31
super(person, target)
-
end
-
-
1
def relayable_options
-
31
{:poll => @target.poll, :poll_answer => @poll_answer}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PollParticipationSignature < ApplicationRecord
-
1
include Diaspora::Signature
-
-
1
self.primary_key = :poll_participation_id
-
1
belongs_to :poll_participation
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Post < ApplicationRecord
-
1
self.include_root_in_json = false
-
-
1
include ApplicationHelper
-
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Federated::Fetchable
-
-
1
include Diaspora::Likeable
-
1
include Diaspora::Commentable
-
1
include Diaspora::Shareable
-
1
include Diaspora::MentionsContainer
-
-
1
has_many :participations, dependent: :delete_all, as: :target, inverse_of: :target
-
1
has_many :participants, through: :participations, source: :author
-
-
1
attr_accessor :user_like
-
-
1
has_many :reports, as: :item
-
-
1
has_many :reshares, class_name: "Reshare", foreign_key: :root_guid, primary_key: :guid
-
1
has_many :resharers, class_name: "Person", through: :reshares, source: :author
-
-
1
belongs_to :o_embed_cache, optional: true
-
1
belongs_to :open_graph_cache, optional: true
-
-
1
validates_uniqueness_of :id
-
-
1
after_create do
-
2611
self.touch(:interacted_at)
-
end
-
-
1
before_destroy do
-
29
reshares.update_all(root_guid: nil) # rubocop:disable Rails/SkipsModelValidations
-
end
-
-
#scopes
-
1
scope :includes_for_a_stream, -> {
-
136
includes(:o_embed_cache,
-
:open_graph_cache,
-
{:author => :profile},
-
:mentions => {:person => :profile}
-
) #note should include root and photos, but i think those are both on status_message
-
}
-
-
49
scope :all_public, -> { where(public: true) }
-
-
1
scope :all_local_public, -> {
-
4
where(" exists (
-
select 1 from people where posts.author_id = people.id
-
and people.pod_id is null)
-
and posts.public = true")
-
}
-
-
1
scope :commented_by, ->(person) {
-
5
select('DISTINCT posts.*')
-
.joins(:comments)
-
.where(:comments => {:author_id => person.id})
-
}
-
-
1
scope :liked_by, ->(person) {
-
5
joins(:likes).where(:likes => {:author_id => person.id})
-
}
-
-
1
scope :subscribed_by, ->(user) {
-
36
joins(:participations).where(participations: {author_id: user.person_id})
-
}
-
-
2
scope :reshares, -> { where(type: "Reshare") }
-
-
1
scope :reshared_by, ->(person) {
-
# we join on the same table, Rails renames "posts" to "reshares_posts" for the right table
-
2
joins(:reshares).where(reshares_posts: {author_id: person.id})
-
}
-
-
1
def post_type
-
180
self.class.name
-
end
-
-
1
def root; end
-
2
def photos; []; end
-
-
#prevents error when trying to access @post.address in a post different than Reshare and StatusMessage types;
-
#check PostPresenter
-
1
def address
-
end
-
-
1
def poll
-
end
-
-
1
def self.excluding_blocks(user)
-
90
people = user.blocks.map{|b| b.person_id}
-
84
scope = all
-
-
84
if people.any?
-
6
scope = scope.where("posts.author_id NOT IN (?)", people)
-
end
-
-
84
scope
-
end
-
-
1
def self.excluding_hidden_shareables(user)
-
95
scope = all
-
95
if user.has_hidden_shareables_of_type?
-
2
scope = scope.where('posts.id NOT IN (?)', user.hidden_shareables["#{self.base_class}"])
-
end
-
95
scope
-
end
-
-
1
def self.excluding_hidden_content(user)
-
83
excluding_blocks(user).excluding_hidden_shareables(user)
-
end
-
-
1
def self.for_a_stream(max_time, order, user=nil, ignore_blocks=false)
-
137
scope = self.for_visible_shareable_sql(max_time, order).
-
includes_for_a_stream
-
-
137
if user.present?
-
73
if ignore_blocks
-
11
scope = scope.excluding_hidden_shareables(user)
-
else
-
62
scope = scope.excluding_hidden_content(user)
-
end
-
end
-
-
137
scope
-
end
-
-
1
def reshare_for(user)
-
88
return unless user
-
66
reshares.find_by(author_id: user.person.id)
-
end
-
-
1
def like_for(user)
-
88
return unless user
-
66
likes.find_by(author_id: user.person.id)
-
end
-
-
#############
-
-
# @return [Integer]
-
1
def update_reshares_counter
-
155
self.class.where(id: id).update_all(reshares_count: reshares.count)
-
end
-
-
1
def self.diaspora_initialize(params)
-
1331
new(params.to_hash.stringify_keys.slice(*column_names, "author"))
-
end
-
-
1
def comment_email_subject
-
I18n.t('notifier.a_post_you_shared')
-
end
-
-
1
def nsfw
-
197
self.author.profile.nsfw?
-
end
-
-
1
def subscribers
-
1342
super.tap do |subscribers|
-
1342
subscribers.concat(resharers).concat(participants) if public?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Profile < ApplicationRecord
-
1
MAX_TAGS = 5
-
1
self.include_root_in_json = false
-
-
1
include Diaspora::Federated::Base
-
1
include Diaspora::Taggable
-
-
1
attr_accessor :tag_string
-
1
acts_as_ordered_taggable
-
1
extract_tags_from :tag_string
-
1
validates :tag_list, :length => { :maximum => 5 }
-
-
1
before_save :strip_names
-
1
after_validation :strip_names
-
-
1
validates :first_name, :length => { :maximum => 32 }
-
1
validates :last_name, :length => { :maximum => 32 }
-
1
validates :location, :length => { :maximum =>255 }
-
1
validates :gender, length: {maximum: 255}
-
-
1
validates_format_of :first_name, :with => /\A[^;]+\z/, :allow_blank => true
-
1
validates_format_of :last_name, :with => /\A[^;]+\z/, :allow_blank => true
-
1
validate :max_tags
-
1
validate :valid_birthday
-
-
1
belongs_to :person
-
1
before_validation do
-
12793
self.tag_string = self.tag_string.split[0..4].join(' ')
-
12793
self.build_tags
-
end
-
-
1
before_save do
-
10562
self.build_tags
-
10562
self.construct_full_name
-
end
-
-
1
def subscribers
-
45
Person.joins(:contacts).where(contacts: {user_id: person.owner_id})
-
end
-
-
1
def public?
-
46
public_details?
-
end
-
-
1
def diaspora_handle
-
#get the parent diaspora handle, unless we want to access a profile without a person
-
146
(self.person) ? self.person.diaspora_handle : self[:diaspora_handle]
-
end
-
-
1
def image_url(size: :thumb_large, fallback_to_default: true)
-
3077
result = if size == :thumb_medium && self[:image_url_medium]
-
321
self[:image_url_medium]
-
2756
elsif size == :thumb_small && self[:image_url_small]
-
225
self[:image_url_small]
-
else
-
2531
self[:image_url]
-
end
-
-
3077
if result
-
616
if AppConfig.privacy.camo.proxy_remote_pod_images?
-
Diaspora::Camo.image_url(result)
-
else
-
616
result
-
end
-
2461
elsif fallback_to_default
-
2369
ActionController::Base.helpers.image_path("user/default.png")
-
end
-
end
-
-
1
def from_omniauth_hash(omniauth_user_hash)
-
7
mappings = {"description" => "bio",
-
'image' => 'image_url',
-
'name' => 'first_name',
-
'location' => 'location',
-
}
-
-
27
update_hash = Hash[ omniauth_user_hash.map {|k, v| [mappings[k], v] } ]
-
-
19
self.attributes.merge(update_hash){|key, old, new| old.blank? ? new : old}
-
end
-
-
1
def image_url=(url)
-
704
super(build_image_url(url))
-
end
-
-
1
def image_url_small=(url)
-
695
super(build_image_url(url))
-
end
-
-
1
def image_url_medium=(url)
-
695
super(build_image_url(url))
-
end
-
-
1
def date= params
-
21
if %w(month day).all? {|key| params[key].present? }
-
5
params["year"] = "1004" if params["year"].blank?
-
5
if Date.valid_civil?(params["year"].to_i, params["month"].to_i, params["day"].to_i)
-
2
self.birthday = Date.new(params["year"].to_i, params["month"].to_i, params["day"].to_i)
-
else
-
3
@invalid_birthday_date = true
-
end
-
8
elsif %w(year month day).all? {|key| params[key].blank? }
-
1
self.birthday = nil
-
end
-
end
-
-
1
def bio_message
-
58
@bio_message ||= Diaspora::MessageRenderer.new(bio)
-
end
-
-
1
def location_message
-
58
@location_message ||= Diaspora::MessageRenderer.new(location)
-
end
-
-
1
def tag_string
-
62253
@tag_string ||= tags.pluck(:name).map {|tag| "##{tag}" }.join(" ")
-
end
-
-
# Constructs a full name by joining #first_name and #last_name
-
# @return [String] A full name
-
1
def construct_full_name
-
10562
self.full_name = [self.first_name, self.last_name].join(' ').downcase.strip
-
10562
self.full_name
-
end
-
-
1
def tombstone!
-
128
@tag_string = nil
-
128
self.taggings.delete_all
-
128
clearable_fields.each do |field|
-
1792
self[field] = nil
-
end
-
128
self[:searchable] = false
-
128
self.save
-
end
-
-
1
protected
-
1
def strip_names
-
23355
self.first_name.strip! if self.first_name
-
23355
self.last_name.strip! if self.last_name
-
end
-
-
1
def max_tags
-
12793
if self.tag_string.count('#') > 5
-
errors[:base] << 'Profile cannot have more than five tags'
-
end
-
end
-
-
1
def valid_birthday
-
12793
if @invalid_birthday_date
-
1
errors.add(:birthday)
-
1
@invalid_birthday_date = nil
-
end
-
end
-
-
1
private
-
-
1
def clearable_fields
-
130
attributes.keys - %w[id created_at updated_at person_id tag_list]
-
end
-
-
1
def build_image_url(url)
-
2094
return nil if url.blank? || url.match(/user\/default/)
-
1973
return url if url.match(/^https?:\/\//)
-
4
"#{AppConfig.pod_uri.to_s.chomp('/')}#{url}"
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Reference < ApplicationRecord
-
1
belongs_to :source, polymorphic: true
-
1
belongs_to :target, polymorphic: true
-
1
validates :target_id, uniqueness: {scope: %i[target_type source_id source_type]}
-
-
1
module Source
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
3
after_create :create_references
-
3
has_many :references, as: :source, dependent: :destroy
-
end
-
-
1
def create_references
-
3256
text&.scan(DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX)&.each do |author, type, guid|
-
24
add_reference(author, type, guid)
-
end
-
end
-
-
1
private
-
-
1
def add_reference(author, type, guid)
-
24
entity = Diaspora::EntityFinder.new(type, guid).find
-
21
references.find_or_create_by(target: entity) if entity&.diaspora_handle == author
-
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
-
3
logger.warn "ignoring invalid diaspora-url: diaspora://#{author}/#{type}/#{guid}: #{e.class}: #{e.message}"
-
end
-
end
-
-
1
module Target
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
has_many :referenced_by, as: :target, class_name: "Reference", dependent: :destroy
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Report < ApplicationRecord
-
1
validates :user_id, presence: true
-
1
validates :item_id, presence: true
-
1
validates :item_type, presence: true, inclusion: {
-
in: %w(Post Comment), message: "Type should match `Post` or `Comment`!"}
-
1
validates :text, presence: true
-
-
1
validate :entry_does_not_exist, :on => :create
-
1
validate :post_or_comment_does_exist, :on => :create
-
-
1
belongs_to :user
-
1
belongs_to :post, optional: true
-
1
belongs_to :comment, optional: true
-
1
belongs_to :item, polymorphic: true
-
-
1
after_commit :send_report_notification, :on => :create
-
-
1
def reported_author
-
item.author if item
-
end
-
-
1
def entry_does_not_exist
-
50
return unless Report.where(item_id: item_id, item_type: item_type).exists?(user_id: user_id)
-
-
3
errors.add(:base, "You cannot report the same post twice.")
-
end
-
-
1
def post_or_comment_does_exist
-
50
return unless Post.find_by(id: item_id).nil? && Comment.find_by(id: item_id).nil?
-
-
3
errors.add(:base, "Post or comment was already deleted or doesn't exists.")
-
end
-
-
1
def destroy_reported_item
-
4
case item
-
when Post
-
2
if item.author.local?
-
2
item.author.owner.retract(item)
-
else
-
item.destroy
-
end
-
when Comment
-
2
if item.author.local?
-
2
item.author.owner.retract(item)
-
elsif item.parent.author.local?
-
item.parent.author.owner.retract(item)
-
else
-
item.destroy
-
end
-
end
-
4
mark_as_reviewed
-
end
-
-
1
def mark_as_reviewed
-
6
Report.where(item_id: item_id, item_type: item_type).update_all(reviewed: true)
-
end
-
-
1
def send_report_notification
-
42
Workers::Mail::ReportWorker.perform_async(id)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Reshare < Post
-
1
belongs_to :root, class_name: "Post", foreign_key: :root_guid, primary_key: :guid, optional: true
-
1
validate :root_must_be_public
-
168
validates :root, presence: true, on: :create, if: proc {|reshare| reshare.author.local? }
-
1
validates :root_guid, uniqueness: {scope: :author_id}, allow_nil: true
-
1
delegate :author, to: :root, prefix: true
-
-
1
before_validation do
-
187
self.public = true
-
end
-
-
1
after_commit :on => :create do
-
155
self.root.update_reshares_counter if self.root.present?
-
end
-
-
1
after_destroy do
-
self.root.update_reshares_counter if self.root.present?
-
end
-
-
1
acts_as_api
-
1
api_accessible :backbone do |t|
-
1
t.add :id
-
1
t.add :guid
-
1
t.add :author
-
1
t.add :created_at
-
end
-
-
1
def root_diaspora_id
-
45
root.try(:author).try(:diaspora_handle)
-
end
-
-
1
delegate :o_embed_cache, :open_graph_cache,
-
:message, :nsfw,
-
to: :absolute_root, allow_nil: true
-
-
1
def text
-
156
absolute_root.try(:text) || ""
-
end
-
-
1
def mentioned_people
-
20
absolute_root.try(:mentioned_people) || super
-
end
-
-
1
def photos
-
20
absolute_root.try(:photos) || super
-
end
-
-
1
def post_location
-
{
-
22
address: absolute_root.try(:location).try(:address),
-
lat: absolute_root.try(:location).try(:lat),
-
lng: absolute_root.try(:location).try(:lng)
-
}
-
end
-
-
1
def poll
-
32
absolute_root.try(:poll) || super
-
end
-
-
1
def comment_email_subject
-
1
I18n.t('reshares.comment_email_subject', :resharer => author.name, :author => root.author_name)
-
end
-
-
1
def absolute_root
-
482
@absolute_root ||= self
-
482
@absolute_root = @absolute_root.root while @absolute_root.is_a? Reshare
-
482
@absolute_root
-
end
-
-
1
def receive(recipient_user_ids)
-
16
super(recipient_user_ids)
-
-
16
root.author.owner.participate!(self) if root.author.local?
-
end
-
-
1
def subscribers
-
56
super.tap {|people| root.try {|root| people << root.author } }
-
end
-
-
1
private
-
-
1
def root_must_be_public
-
187
errors.add(:base, "Only posts which are public may be reshared.") if root && !root.public
-
end
-
end
-
# frozen_string_literal: true
-
-
# NOTE add the person object you want to attach role to...
-
-
1
class Role < ApplicationRecord
-
1
belongs_to :person
-
-
1
validates :name, uniqueness: {scope: :person_id}
-
1
validates :name, inclusion: {in: %w(admin moderator spotlight)}
-
-
114
scope :admins, -> { where(name: "admin") }
-
273
scope :moderators, -> { where(name: %w(moderator admin)) }
-
-
1
def self.is_admin?(person)
-
337
exists?(person_id: person.id, name: "admin")
-
end
-
-
1
def self.add_admin(person)
-
39
find_or_create_by(person_id: person.id, name: "admin")
-
end
-
-
1
def self.remove_admin(person)
-
find_by(person_id: person.id, name: "admin").destroy
-
end
-
-
1
def self.moderator?(person)
-
263
moderators.exists?(person_id: person.id)
-
end
-
-
1
def self.moderator_only?(person)
-
20
exists?(person_id: person.id, name: "moderator")
-
end
-
-
1
def self.add_moderator(person)
-
17
find_or_create_by(person_id: person.id, name: "moderator")
-
end
-
-
1
def self.remove_moderator(person)
-
find_by(person_id: person.id, name: "moderator").destroy
-
end
-
-
1
def self.spotlight?(person)
-
20
exists?(person_id: person.id, name: "spotlight")
-
end
-
-
1
def self.add_spotlight(person)
-
3
find_or_create_by(person_id: person.id, name: "spotlight")
-
end
-
-
1
def self.remove_spotlight(person)
-
find_by(person_id: person.id, name: "spotlight").destroy
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class Service < ApplicationRecord
-
1
attr_accessor :provider, :info, :access_level
-
-
1
belongs_to :user
-
1
validates_uniqueness_of :uid, :scope => :type
-
-
1
def profile_photo_url
-
nil
-
end
-
-
1
def post_opts(post)
-
# don't do anything (should be overridden by service extensions)
-
end
-
-
1
class << self
-
-
1
def titles(service_strings)
-
31
service_strings.map {|s| "Services::#{s.titleize}"}
-
end
-
-
1
def first_from_omniauth( auth_hash )
-
1
@@auth = auth_hash
-
1
find_by(type: service_type, uid: options[:uid])
-
end
-
-
1
def initialize_from_omniauth( auth_hash )
-
6
@@auth = auth_hash
-
6
service_type.constantize.new( options )
-
end
-
-
1
def auth
-
84
@@auth
-
end
-
-
1
def service_type
-
7
"Services::#{options[:provider].camelize}"
-
end
-
-
1
def options
-
{
-
14
nickname: auth['info']['nickname'],
-
access_token: auth['credentials']['token'],
-
access_secret: auth['credentials']['secret'],
-
uid: auth['uid'],
-
provider: auth['provider'],
-
info: auth['info']
-
}
-
end
-
-
1
private :auth, :service_type, :options
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Services
-
1
class Tumblr < Service
-
1
MAX_CHARACTERS = 1000
-
-
1
def provider
-
11
"tumblr"
-
end
-
-
1
def post(post, url="") # rubocop:disable Metrics/AbcSize
-
2
return true if post.nil? # return if post is deleted while waiting in queue
-
-
2
body = build_tumblr_post(post, url)
-
2
user_info = JSON.parse(client.get("/v2/user/info").body)
-
2
blogs = user_info["response"]["user"]["blogs"]
-
5
primaryblog = blogs.find {|blog| blog["primary"] } || blogs[0]
-
-
2
tumblr_ids = {}
-
-
2
blogurl = URI.parse(primaryblog["url"])
-
2
tumblr_ids[blogurl.host.to_s] = request_to_external_blog(blogurl, body)
-
-
2
post.tumblr_ids = tumblr_ids.to_json
-
2
post.save
-
end
-
-
1
def post_opts(post)
-
2
{tumblr_ids: post.tumblr_ids} if post.tumblr_ids.present?
-
end
-
-
1
def delete_from_service(opts)
-
1
logger.debug "event=delete_from_service type=tumblr sender_id=#{user_id} tumblr_ids=#{opts[:tumblr_ids]}"
-
1
tumblr_posts = JSON.parse(opts[:tumblr_ids])
-
1
tumblr_posts.each do |blog_name, post_id|
-
1
delete_from_tumblr(blog_name, post_id)
-
end
-
end
-
-
1
def build_tumblr_post(post, url)
-
4
{type: "text", format: "markdown", body: tumblr_template(post, url), tags: tags(post), native_inline_images: true}
-
end
-
-
1
private
-
-
1
def client
-
5
@consumer ||= OAuth::Consumer.new(consumer_key, consumer_secret, site: "https://api.tumblr.com")
-
5
@client ||= OAuth::AccessToken.new(@consumer, access_token, access_secret)
-
end
-
-
1
def tumblr_template(post, url)
-
4
photo_html = post.photos.map {|photo| "})\n\n" }.join
-
-
4
"#{photo_html}#{post.message.html(mentioned_people: [])}\n\n[original post](#{url})"
-
end
-
-
1
def tags(post)
-
4
post.tags.pluck(:name).join(",").to_s
-
end
-
-
1
def delete_from_tumblr(blog_name, service_post_id)
-
1
client.post("/v2/blog/#{blog_name}/post/delete", "id" => service_post_id)
-
end
-
-
1
def request_to_external_blog(blogurl, body)
-
2
resp = client.post("/v2/blog/#{blogurl.host}/post", body)
-
2
JSON.parse(resp.body)["response"]["id"] if resp.code == "201"
-
end
-
-
1
def consumer_key
-
3
AppConfig.services.tumblr.key
-
end
-
-
1
def consumer_secret
-
3
AppConfig.services.tumblr.secret
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Services::Twitter < Service
-
1
include Rails.application.routes.url_helpers
-
-
1
MAX_CHARACTERS = 280
-
1
SHORTENED_URL_LENGTH = 23
-
1
LINK_PATTERN = %r{https?://\S+}
-
-
1
def provider
-
15
"twitter"
-
end
-
-
1
def post post, url=''
-
3
logger.debug "event=post_to_service type=twitter sender_id=#{user_id} post=#{post.guid}"
-
3
tweet = attempt_post post
-
3
post.tweet_id = tweet.id
-
3
post.save
-
end
-
-
1
def profile_photo_url
-
1
client.user(nickname).profile_image_url_https "original"
-
end
-
-
1
def post_opts(post)
-
3
{tweet_id: post.tweet_id} if post.tweet_id.present?
-
end
-
-
1
def delete_from_service(opts)
-
logger.debug "event=delete_from_service type=twitter sender_id=#{user_id} tweet_id=#{opts[:tweet_id]}"
-
delete_from_twitter(opts[:tweet_id])
-
end
-
-
1
private
-
-
1
def client
-
4
@client ||= Twitter::REST::Client.new do |config|
-
4
config.consumer_key = AppConfig.services.twitter.key
-
4
config.consumer_secret = AppConfig.services.twitter.secret
-
4
config.access_token = access_token
-
4
config.access_token_secret = access_secret
-
end
-
end
-
-
1
def attempt_post post, retry_count=0
-
3
message = build_twitter_post post, retry_count
-
3
client.update message
-
rescue Twitter::Error::Forbidden => e
-
if ! e.message.include? 'is over 140' || retry_count == 20
-
raise e
-
else
-
attempt_post post, retry_count+1
-
end
-
end
-
-
1
def build_twitter_post post, retry_count=0
-
12
max_characters = MAX_CHARACTERS - retry_count
-
-
12
post_text = post.message.plain_text_without_markdown
-
12
truncate_and_add_post_link post, post_text, max_characters
-
end
-
-
1
def truncate_and_add_post_link post, post_text, max_characters
-
12
return post_text unless needs_link? post, post_text, max_characters
-
-
5
post_url = short_post_url(
-
post,
-
protocol: AppConfig.pod_uri.scheme,
-
host: AppConfig.pod_uri.authority
-
)
-
-
5
truncated_text = post_text.truncate max_characters - SHORTENED_URL_LENGTH - 1
-
5
truncated_text = restore_truncated_url truncated_text, post_text, max_characters
-
-
5
"#{truncated_text} #{post_url}"
-
end
-
-
1
def needs_link? post, post_text, max_characters
-
12
adjust_length_for_urls(post_text) > max_characters || post.photos.any?
-
end
-
-
1
def adjust_length_for_urls post_text
-
12
real_length = post_text.length
-
-
12
URI.extract(post_text, ['http','https']) do |url|
-
# add or subtract from real length - urls for tweets are always
-
# shortened to SHORTENED_URL_LENGTH
-
4
if url.length >= SHORTENED_URL_LENGTH
-
4
real_length -= url.length - SHORTENED_URL_LENGTH
-
else
-
real_length += SHORTENED_URL_LENGTH - url.length
-
end
-
end
-
-
12
real_length
-
end
-
-
1
def restore_truncated_url truncated_text, post_text, max_characters
-
5
return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
-
-
url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
-
truncated_text = post_text.truncate(
-
max_characters - SHORTENED_URL_LENGTH + 2,
-
separator: ' ', omission: ''
-
)
-
-
"#{truncated_text} #{url} ..."
-
end
-
-
1
def delete_from_twitter service_post_id
-
client.destroy_status service_post_id
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Services
-
1
class Wordpress < Service
-
1
MAX_CHARACTERS = 1000
-
-
1
attr_accessor :username, :password, :host, :path
-
-
# uid = blog_id
-
-
1
def provider
-
1
"wordpress"
-
end
-
-
1
def post(post, _url="")
-
1
res = Faraday.new(url: "https://public-api.wordpress.com").post do |req|
-
1
req.url "/rest/v1/sites/#{uid}/posts/new"
-
1
req.body = post_body(post).to_json
-
1
req.headers["Authorization"] = "Bearer #{access_token}"
-
1
req.headers["Content-Type"] = "application/json"
-
end
-
-
1
JSON.parse res.env[:body]
-
end
-
-
1
def post_body(post)
-
{
-
3
title: post.message.title,
-
content: post.message.markdownified(disable_hovercards: true)
-
}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ShareVisibility < ApplicationRecord
-
1
belongs_to :user
-
1
belongs_to :shareable, polymorphic: :true
-
-
1
scope :for_a_user, ->(user) {
-
33
where(user_id: user.id)
-
}
-
-
1
scope :for_shareable, ->(shareable) {
-
799
where(shareable_id: shareable.id, shareable_type: shareable.class.base_class.to_s)
-
}
-
-
1
validate :not_public
-
-
# Perform a batch import, given a set of users and a shareable
-
# @note performs a bulk insert in mySQL; performs linear insertions in postgres
-
# @param user_ids [Array<Integer>] Recipients
-
# @param share [Shareable]
-
# @return [void]
-
1
def self.batch_import(user_ids, share)
-
801
return false if share.public?
-
-
799
user_ids -= ShareVisibility.for_shareable(share).where(user_id: user_ids).pluck(:user_id)
-
799
return false if user_ids.empty?
-
-
797
create_visilities(user_ids, share)
-
end
-
-
1
private
-
-
1
private_class_method def self.create_visilities(user_ids, share)
-
797
if AppConfig.postgres?
-
797
user_ids.each do |user_id|
-
1047
ShareVisibility.find_or_create_by(
-
user_id: user_id,
-
shareable_id: share.id,
-
shareable_type: share.class.base_class.to_s
-
)
-
end
-
else
-
new_share_visibilities_data = user_ids.map do |user_id|
-
[user_id, share.id, share.class.base_class.to_s]
-
end
-
ShareVisibility.import(%i(user_id shareable_id shareable_type), new_share_visibilities_data)
-
end
-
end
-
-
1
def not_public
-
1063
errors[:base] << "Cannot create visibility for a public object" if shareable.public?
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class SignatureOrder < ApplicationRecord
-
1
validates :order, presence: true, uniqueness: true
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class StatusMessage < Post
-
1
include Diaspora::Taggable
-
-
1
include Reference::Source
-
1
include Reference::Target
-
-
1
include PeopleHelper
-
-
1
acts_as_taggable_on :tags
-
1
extract_tags_from :text
-
-
3
validates_length_of :text, :maximum => 65535, :message => proc {|p, v| I18n.t('status_messages.too_long', :count => 65535, :current_length => v[:value].length)}
-
-
# don't allow creation of empty status messages
-
1
validate :presence_of_content, on: :create
-
-
1
has_many :photos, :dependent => :destroy, :foreign_key => :status_message_guid, :primary_key => :guid
-
-
1
has_one :location
-
1
has_one :poll, autosave: true, dependent: :destroy
-
1
has_many :poll_participations, through: :poll
-
-
1
attr_accessor :oembed_url
-
1
attr_accessor :open_graph_url
-
-
1
after_commit :queue_gather_oembed_data, :on => :create, :if => :contains_oembed_url_in_text?
-
1
after_commit :queue_gather_open_graph_data, :on => :create, :if => :contains_open_graph_url_in_text?
-
-
#scopes
-
1
scope :where_person_is_mentioned, ->(person) {
-
33
owned_or_visible_by_user(person.owner).joins(:mentions).where(mentions: {person_id: person.id})
-
}
-
-
1
def self.model_name
-
28
Post.model_name
-
end
-
-
1
def self.guids_for_author(person)
-
6
Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
-
end
-
-
1
def self.user_tag_stream(user, tag_ids)
-
16
owned_or_visible_by_user(user).tag_stream(tag_ids)
-
end
-
-
1
def self.public_tag_stream(tag_ids)
-
31
all_public.select("DISTINCT #{table_name}.*").tag_stream(tag_ids)
-
end
-
-
1
def self.tag_stream(tag_ids)
-
47
joins(:taggings).where("taggings.tag_id IN (?)", tag_ids)
-
end
-
-
1
def nsfw
-
204
!!(text.try(:match, /#nsfw/i) || super) # rubocop:disable Style/DoubleNegation
-
end
-
-
1
def comment_email_subject
-
21
if message.present?
-
20
message.title
-
1
elsif photos.present?
-
1
I18n.t("posts.show.photos_by", count: photos.size, author: author_name)
-
end
-
end
-
-
1
def first_photo_url(*args)
-
photos.first.url(*args)
-
end
-
-
1
def text_and_photos_blank?
-
2382
text.blank? && photos.blank?
-
end
-
-
1
def queue_gather_oembed_data
-
17
Workers::GatherOEmbedData.perform_async(self.id, self.oembed_url)
-
end
-
-
1
def queue_gather_open_graph_data
-
39
Workers::GatherOpenGraphData.perform_async(self.id, self.open_graph_url)
-
end
-
-
1
def contains_oembed_url_in_text?
-
4717
urls = self.message.urls
-
4835
self.oembed_url = urls.find{ |url| !TRUSTED_OEMBED_PROVIDERS.find(url).nil? }
-
end
-
-
1
def contains_open_graph_url_in_text?
-
2359
return nil if self.contains_oembed_url_in_text?
-
2341
self.open_graph_url = self.message.urls[0]
-
end
-
-
1
def post_location
-
{
-
150
address: location.try(:address),
-
lat: location.try(:lat),
-
lng: location.try(:lng)
-
}
-
end
-
-
1
def receive(recipient_user_ids)
-
984
super(recipient_user_ids)
-
-
988
photos.each {|photo| photo.receive(recipient_user_ids) }
-
end
-
-
# Note: the next two methods can be safely removed once changes from #6818 are deployed on every pod
-
# see StatusMessageCreationService#dispatch
-
# Only includes those people, to whom we're going to send a federation entity
-
# (and doesn't define exhaustive list of people who can receive it)
-
1
def people_allowed_to_be_mentioned
-
125
@aspects_ppl ||=
-
90
if public?
-
54
:all
-
else
-
36
Contact.joins(:aspect_memberships).where(aspect_memberships: {aspect: aspects}).distinct.pluck(:person_id)
-
end
-
end
-
-
1
def filter_mentions
-
88
return if people_allowed_to_be_mentioned == :all
-
35
update(text: Diaspora::Mentionable.filter_people(text, people_allowed_to_be_mentioned))
-
end
-
-
1
private
-
-
1
def presence_of_content
-
2379
errors.add(:base, "Cannot create a StatusMessage without content") if text_and_photos_blank?
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
class TagFollowing < ApplicationRecord
-
1
belongs_to :user
-
1
belongs_to :tag, :class_name => "ActsAsTaggableOn::Tag"
-
-
1
validates_uniqueness_of :tag_id, :scope => :user_id
-
-
1
def self.user_is_following?(user, tagname)
-
4
tagname.nil? ? false : joins(:tag).where(:tags => {:name => tagname.downcase}).where(:user_id => user.id).exists?
-
end
-
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
require "attr_encrypted"
-
-
class User < ApplicationRecord
-
include Connecting
-
include Querying
-
include SocialActions
-
-
apply_simple_captcha :message => I18n.t('simple_captcha.message.failed'), :add_to_base => true
-
-
scope :logged_in_since, ->(time) { where('last_seen > ?', time) }
-
scope :monthly_actives, ->(time = Time.now) { logged_in_since(time - 1.month) }
-
scope :daily_actives, ->(time = Time.now) { logged_in_since(time - 1.day) }
-
scope :yearly_actives, ->(time = Time.now) { logged_in_since(time - 1.year) }
-
scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
-
scope :active, -> { joins(:person).where(people: {closed_account: false}) }
-
-
attr_encrypted :otp_secret, if: false, prefix: "plain_"
-
-
devise :two_factor_authenticatable,
-
:two_factor_backupable,
-
otp_backup_code_length: 16,
-
otp_number_of_backup_codes: 10
-
-
devise :registerable,
-
:recoverable, :rememberable, :trackable, :validatable,
-
:lockable, :lastseenable, :lock_strategy => :none, :unlock_strategy => :none
-
-
before_validation :strip_and_downcase_username
-
before_validation :set_current_language, :on => :create
-
before_validation :set_default_color_theme, on: :create
-
-
validates :username, presence: true, uniqueness: true, format: {with: /\A[A-Za-z0-9_.\-]+\z/},
-
length: {maximum: 32}, exclusion: {in: AppConfig.settings.username_blacklist}
-
validates_inclusion_of :language, :in => AVAILABLE_LANGUAGE_CODES
-
validates :color_theme, inclusion: {in: AVAILABLE_COLOR_THEMES}, allow_blank: true
-
validates_format_of :unconfirmed_email, :with => Devise.email_regexp, :allow_blank => true
-
-
validate :unconfirmed_email_quasiuniqueness
-
-
validates :person, presence: true
-
validates_associated :person
-
validate :no_person_with_same_username
-
-
serialize :hidden_shareables, Hash
-
serialize :otp_backup_codes, Array
-
-
has_one :person, inverse_of: :owner, foreign_key: :owner_id
-
has_one :profile, through: :person
-
-
delegate :guid, :public_key, :posts, :photos, :owns?, :image_url,
-
:diaspora_handle, :name, :atom_url, :profile_url, :profile, :url,
-
:first_name, :last_name, :full_name, :gender, :participations, to: :person
-
delegate :id, :guid, to: :person, prefix: true
-
-
has_many :aspects, -> { order('order_id ASC') }
-
-
belongs_to :auto_follow_back_aspect, class_name: "Aspect", optional: true
-
belongs_to :invited_by, class_name: "User", optional: true
-
-
has_many :invited_users, class_name: "User", inverse_of: :invited_by, foreign_key: :invited_by_id
-
-
has_many :aspect_memberships, :through => :aspects
-
-
has_many :contacts
-
has_many :contact_people, :through => :contacts, :source => :person
-
-
has_many :services
-
-
has_many :user_preferences
-
-
has_many :tag_followings
-
has_many :followed_tags, -> { order('tags.name') }, :through => :tag_followings, :source => :tag
-
-
has_many :blocks
-
has_many :ignored_people, :through => :blocks, :source => :person
-
-
has_many :conversation_visibilities, through: :person
-
has_many :conversations, through: :conversation_visibilities
-
-
has_many :notifications, :foreign_key => :recipient_id
-
-
has_many :reports
-
-
has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier"
-
has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
-
has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"
-
-
has_many :share_visibilities
-
-
before_save :guard_unconfirmed_email
-
-
after_save :remove_invalid_unconfirmed_emails
-
-
before_destroy do
-
raise "Never destroy users!"
-
end
-
-
def self.all_sharing_with_person(person)
-
User.joins(:contacts).where(:contacts => {:person_id => person.id})
-
end
-
-
def unread_notifications
-
notifications.where(:unread => true)
-
end
-
-
def unread_message_count
-
ConversationVisibility.where(person_id: self.person_id).sum(:unread)
-
end
-
-
def process_invite_acceptence(invite)
-
self.invited_by = invite.user
-
invite.use! unless AppConfig.settings.enable_registrations?
-
end
-
-
def invitation_code
-
InvitationCode.find_or_create_by(user_id: self.id)
-
end
-
-
def hidden_shareables
-
self[:hidden_shareables] ||= {}
-
end
-
-
def add_hidden_shareable(key, share_id, opts={})
-
if self.hidden_shareables.has_key?(key)
-
self.hidden_shareables[key] << share_id
-
else
-
self.hidden_shareables[key] = [share_id]
-
end
-
self.save unless opts[:batch]
-
self.hidden_shareables
-
end
-
-
def remove_hidden_shareable(key, share_id)
-
if self.hidden_shareables.has_key?(key)
-
self.hidden_shareables[key].delete(share_id)
-
end
-
end
-
-
def is_shareable_hidden?(shareable)
-
shareable_type = shareable.class.base_class.name
-
if self.hidden_shareables.has_key?(shareable_type)
-
self.hidden_shareables[shareable_type].include?(shareable.id.to_s)
-
else
-
false
-
end
-
end
-
-
def toggle_hidden_shareable(share)
-
share_id = share.id.to_s
-
key = share.class.base_class.to_s
-
if self.hidden_shareables.has_key?(key) && self.hidden_shareables[key].include?(share_id)
-
self.remove_hidden_shareable(key, share_id)
-
self.save
-
false
-
else
-
self.add_hidden_shareable(key, share_id)
-
self.save
-
true
-
end
-
end
-
-
def has_hidden_shareables_of_type?(t = Post)
-
share_type = t.base_class.to_s
-
self.hidden_shareables[share_type].present?
-
end
-
-
# Copy the method provided by Devise to be able to call it later
-
# from a Sidekiq job
-
alias_method :send_reset_password_instructions!, :send_reset_password_instructions
-
-
def send_reset_password_instructions
-
Workers::ResetPassword.perform_async(self.id)
-
end
-
-
def update_user_preferences(pref_hash)
-
if self.disable_mail
-
UserPreference::VALID_EMAIL_TYPES.each{|x| self.user_preferences.find_or_create_by(email_type: x)}
-
self.disable_mail = false
-
self.save
-
end
-
-
pref_hash.keys.each do |key|
-
if pref_hash[key] == 'true'
-
self.user_preferences.find_or_create_by(email_type: key)
-
else
-
block = user_preferences.find_by(email_type: key)
-
if block
-
block.destroy
-
end
-
end
-
end
-
end
-
-
def strip_and_downcase_username
-
if username.present?
-
username.strip!
-
username.downcase!
-
end
-
end
-
-
def disable_getting_started
-
self.update_attribute(:getting_started, false) if self.getting_started?
-
end
-
-
def set_current_language
-
self.language = I18n.locale.to_s if self.language.blank?
-
end
-
-
def set_default_color_theme
-
self.color_theme ||= AppConfig.settings.default_color_theme
-
end
-
-
# This override allows a user to enter either their email address or their username into the username field.
-
# @return [User] The user that matches the username/email condition.
-
# @return [nil] if no user matches that condition.
-
def self.find_for_database_authentication(conditions={})
-
conditions = conditions.dup
-
conditions[:username] = conditions[:username].downcase
-
if conditions[:username] =~ /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i # email regex
-
conditions[:email] = conditions.delete(:username)
-
end
-
where(conditions).first
-
end
-
-
def confirm_email(token)
-
return false if token.blank? || token != confirm_email_token
-
self.email = unconfirmed_email
-
save
-
end
-
-
######## Posting ########
-
def build_post(class_name, opts={})
-
opts[:author] = person
-
-
model_class = class_name.to_s.camelize.constantize
-
model_class.diaspora_initialize(opts)
-
end
-
-
def dispatch_post(post, opts={})
-
logger.info "user:#{id} dispatching #{post.class}:#{post.guid}"
-
Diaspora::Federation::Dispatcher.defer_dispatch(self, post, opts)
-
end
-
-
def update_post(post, post_hash={})
-
if self.owns? post
-
post.update(post_hash)
-
self.dispatch_post(post)
-
end
-
end
-
-
def add_to_streams(post, aspects_to_insert)
-
aspects_to_insert.each do |aspect|
-
aspect << post
-
end
-
end
-
-
def aspects_from_ids(aspect_ids)
-
if aspect_ids == "all" || aspect_ids == :all
-
self.aspects
-
else
-
aspects.where(:id => aspect_ids).to_a
-
end
-
end
-
-
def post_default_aspects
-
if post_default_public
-
["public"]
-
else
-
aspects.where(post_default: true).to_a
-
end
-
end
-
-
def update_post_default_aspects(post_default_aspect_ids)
-
aspects.each do |aspect|
-
enable = post_default_aspect_ids.include?(aspect.id.to_s)
-
aspect.update_attribute(:post_default, enable)
-
end
-
end
-
-
# Check whether the user has liked a post.
-
# @param [Post] post
-
def liked?(target)
-
if target.likes.loaded?
-
if self.like_for(target)
-
return true
-
else
-
return false
-
end
-
else
-
Like.exists?(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id)
-
end
-
end
-
-
# Get the user's like of a post, if there is one.
-
# @param [Post] post
-
# @return [Like]
-
def like_for(target)
-
if target.likes.loaded?
-
target.likes.find {|like| like.author_id == person.id }
-
else
-
Like.find_by(author_id: person.id, target_type: target.class.base_class.to_s, target_id: target.id)
-
end
-
end
-
-
######### Data export ##################
-
mount_uploader :export, ExportedUser
-
-
def queue_export
-
update exporting: true, export: nil, exported_at: nil
-
Workers::ExportUser.perform_async(id)
-
end
-
-
def perform_export!
-
export = Tempfile.new([username, ".json.gz"], encoding: "ascii-8bit")
-
export.write(compressed_export) && export.close
-
if export.present?
-
update exporting: false, export: export, exported_at: Time.zone.now
-
else
-
update exporting: false
-
end
-
rescue StandardError => e
-
logger.error "Unexpected error while exporting data for '#{username}': #{e.class}: #{e.message}\n" \
-
"#{e.backtrace.first(15).join("\n")}"
-
update exporting: false
-
end
-
-
def compressed_export
-
ActiveSupport::Gzip.compress Diaspora::Exporter.new(self).execute
-
end
-
-
######### Photo export ##################
-
mount_uploader :exported_photos_file, ExportedPhotos
-
-
def queue_export_photos
-
update exporting_photos: true, exported_photos_file: nil, exported_photos_at: nil
-
Workers::ExportPhotos.perform_async(id)
-
end
-
-
def perform_export_photos!
-
PhotoExporter.new(self).perform
-
rescue StandardError => e
-
logger.error "Unexpected error while exporting photos for '#{username}': #{e.class}: #{e.message}\n" \
-
"#{e.backtrace.first(15).join("\n")}"
-
update exporting_photos: false
-
end
-
-
######### Mailer #######################
-
def mail(job, *args)
-
return unless job.present?
-
pref = job.to_s.gsub('Workers::Mail::', '').underscore
-
if(self.disable_mail == false && !self.user_preferences.exists?(:email_type => pref))
-
job.perform_async(*args)
-
end
-
end
-
-
def send_confirm_email
-
return if unconfirmed_email.blank?
-
Workers::Mail::ConfirmEmail.perform_async(id)
-
end
-
-
######### Posts and Such ###############
-
def retract(target)
-
retraction = Retraction.for(target)
-
retraction.defer_dispatch(self)
-
retraction.perform
-
end
-
-
########### Profile ######################
-
def update_profile(params)
-
if photo = params.delete(:photo)
-
photo.update(pending: false) if photo.pending
-
params[:image_url] = photo.url(:thumb_large)
-
params[:image_url_medium] = photo.url(:thumb_medium)
-
params[:image_url_small] = photo.url(:thumb_small)
-
end
-
-
params.stringify_keys!
-
params.slice!(*(Profile.column_names+['tag_string', 'date']))
-
if profile.update(params)
-
deliver_profile_update
-
true
-
else
-
false
-
end
-
end
-
-
def update_profile_with_omniauth( user_info )
-
update_profile( self.profile.from_omniauth_hash( user_info ) )
-
end
-
-
def deliver_profile_update(opts={})
-
Diaspora::Federation::Dispatcher.defer_dispatch(self, profile, opts)
-
end
-
-
def basic_profile_present?
-
tag_followings.any? || profile[:image_url]
-
end
-
-
### Helpers ############
-
def self.build(opts={})
-
u = User.new(opts.except(:person, :id))
-
u.setup(opts)
-
u
-
end
-
-
def self.find_or_build(opts={})
-
user = User.find_by(username: opts[:username])
-
user ||= User.build(opts)
-
user
-
end
-
-
def setup(opts)
-
self.username = opts[:username]
-
self.email = opts[:email]
-
self.language = opts[:language]
-
self.language ||= I18n.locale.to_s
-
self.color_theme = opts[:color_theme]
-
self.color_theme ||= AppConfig.settings.default_color_theme
-
valid?
-
errors = self.errors
-
errors.delete :person
-
return if errors.size > 0
-
-
self.set_person(Person.new((opts[:person] || {}).except(:id)))
-
self.generate_keys
-
self
-
end
-
-
def set_person(person)
-
person.diaspora_handle = "#{self.username}#{User.diaspora_id_host}"
-
self.person = person
-
end
-
-
def self.diaspora_id_host
-
"@#{AppConfig.bare_pod_uri}"
-
end
-
-
def seed_aspects
-
self.aspects.create(:name => I18n.t('aspects.seed.family'))
-
self.aspects.create(:name => I18n.t('aspects.seed.friends'))
-
self.aspects.create(:name => I18n.t('aspects.seed.work'))
-
aq = self.aspects.create(:name => I18n.t('aspects.seed.acquaintances'))
-
-
if AppConfig.settings.autofollow_on_join?
-
begin
-
default_account = Person.find_or_fetch_by_identifier(AppConfig.settings.autofollow_on_join_user)
-
share_with(default_account, aq)
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
logger.warn "Error auto-sharing with #{AppConfig.settings.autofollow_on_join_user}
-
fix autofollow_on_join_user in configuration."
-
end
-
end
-
aq
-
end
-
-
def send_welcome_message
-
return unless AppConfig.settings.welcome_message.enabled? && AppConfig.admins.account?
-
sender_username = AppConfig.admins.account.get
-
sender = User.find_by(username: sender_username)
-
return if sender.nil?
-
conversation = sender.build_conversation(
-
participant_ids: [sender.person.id, person.id],
-
subject: AppConfig.settings.welcome_message.subject.get,
-
message: {text: AppConfig.settings.welcome_message.text.get % {username: username}}
-
)
-
-
Diaspora::Federation::Dispatcher.build(sender, conversation).dispatch if conversation.save
-
end
-
-
def encryption_key
-
OpenSSL::PKey::RSA.new(serialized_private_key)
-
end
-
-
def admin?
-
Role.is_admin?(self.person)
-
end
-
-
def moderator?
-
Role.moderator?(person)
-
end
-
-
def moderator_only?
-
Role.moderator_only?(person)
-
end
-
-
def spotlight?
-
Role.spotlight?(person)
-
end
-
-
def podmin_account?
-
username == AppConfig.admins.account
-
end
-
-
def mine?(target)
-
if target.present? && target.respond_to?(:user_id)
-
return self.id == target.user_id
-
end
-
-
false
-
end
-
-
-
# Ensure that the unconfirmed email isn't already someone's email
-
def unconfirmed_email_quasiuniqueness
-
if User.exists?(["id != ? AND email = ?", id, unconfirmed_email])
-
errors.add(:unconfirmed_email, I18n.t("errors.messages.taken"))
-
end
-
end
-
-
def guard_unconfirmed_email
-
self.unconfirmed_email = nil if unconfirmed_email.blank? || unconfirmed_email == email
-
-
return unless will_save_change_to_unconfirmed_email?
-
-
self.confirm_email_token = unconfirmed_email ? SecureRandom.hex(15) : nil
-
end
-
-
# Whenever email is set, clear all unconfirmed emails which match
-
def remove_invalid_unconfirmed_emails
-
return unless saved_change_to_email?
-
# rubocop:disable Rails/SkipsModelValidations
-
User.where(unconfirmed_email: email).update_all(unconfirmed_email: nil, confirm_email_token: nil)
-
# rubocop:enable Rails/SkipsModelValidations
-
end
-
-
# Generate public/private keys for User and associated Person
-
def generate_keys
-
key_size = (Rails.env == "test" ? 512 : 4096)
-
-
self.serialized_private_key = OpenSSL::PKey::RSA.generate(key_size).to_s if serialized_private_key.blank?
-
-
if self.person && self.person.serialized_public_key.blank?
-
self.person.serialized_public_key = OpenSSL::PKey::RSA.new(self.serialized_private_key).public_key.to_s
-
end
-
end
-
-
def no_person_with_same_username
-
diaspora_id = "#{username}#{User.diaspora_id_host}"
-
return unless username_changed? && Person.exists?(diaspora_handle: diaspora_id)
-
-
errors.add(:base, "That username has already been taken")
-
end
-
-
def close_account!
-
self.person.lock_access!
-
self.lock_access!
-
AccountDeletion.create(person: person)
-
end
-
-
def closed_account?
-
self.person.closed_account
-
end
-
-
def clear_account!
-
clearable_fields.each do |field|
-
self[field] = nil
-
end
-
[:getting_started,
-
:show_community_spotlight_in_stream,
-
:post_default_public].each do |field|
-
self[field] = false
-
end
-
self.remove_export = true
-
self.remove_exported_photos_file = true
-
self[:disable_mail] = true
-
self[:email] = "deletedaccount_#{self[:id]}@example.org"
-
-
random_password = SecureRandom.hex(20)
-
self.password = random_password
-
self.password_confirmation = random_password
-
self.save(:validate => false)
-
end
-
-
def sign_up
-
if AppConfig.settings.captcha.enable?
-
save_with_captcha
-
else
-
save
-
end
-
end
-
-
def flag_for_removal(remove_after)
-
# flag inactive user for future removal
-
if AppConfig.settings.maintenance.remove_old_users.enable?
-
self.remove_after = remove_after
-
self.save
-
end
-
end
-
-
def after_database_authentication
-
# remove any possible remove_after timestamp flag set by maintenance.remove_old_users
-
unless self.remove_after.nil?
-
self.remove_after = nil
-
self.save
-
end
-
end
-
-
def remember_me
-
true
-
end
-
-
private
-
-
def clearable_fields
-
attributes.keys - %w(id username encrypted_password created_at updated_at locked_at
-
serialized_private_key getting_started
-
disable_mail show_community_spotlight_in_stream
-
email remove_after export exporting
-
exported_photos_file exporting_photos)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class User
-
module Connecting
-
# This will create a contact on the side of the sharer and the sharee.
-
# @param [Person] person The person to start sharing with.
-
# @param [Aspect] aspect The aspect to add them to.
-
# @return [Contact] The newly made contact for the passed in person.
-
def share_with(person, aspect)
-
return if blocks.where(person_id: person.id).exists?
-
-
contact = contacts.find_or_initialize_by(person_id: person.id)
-
return false unless contact.valid?
-
-
needs_dispatch = !contact.receiving?
-
contact.receiving = true
-
contact.aspects << aspect
-
contact.save
-
-
if needs_dispatch
-
Diaspora::Federation::Dispatcher.defer_dispatch(self, contact)
-
deliver_profile_update(subscriber_ids: [person.id]) unless person.local?
-
end
-
-
Notifications::StartedSharing.where(recipient_id: id, target: person.id, unread: true)
-
.update_all(unread: false)
-
-
contact
-
end
-
-
def disconnect(contact)
-
logger.info "event=disconnect user=#{diaspora_handle} target=#{contact.person.diaspora_handle}"
-
-
if contact.person.local?
-
raise "FATAL: user entry is missing from the DB. Aborting" if contact.person.owner.nil?
-
contact.person.owner.disconnected_by(contact.user.person)
-
else
-
ContactRetraction.for(contact).defer_dispatch(self)
-
end
-
-
contact.aspect_memberships.delete_all
-
-
disconnect_contact(contact, direction: :receiving, destroy: !contact.sharing)
-
end
-
-
def disconnected_by(person)
-
logger.info "event=disconnected_by user=#{diaspora_handle} target=#{person.diaspora_handle}"
-
contact_for(person).try {|contact| disconnect_contact(contact, direction: :sharing, destroy: !contact.receiving) }
-
end
-
-
private
-
-
def disconnect_contact(contact, direction:, destroy:)
-
if destroy
-
contact.destroy
-
else
-
contact.update(direction => false)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
#TODO: THIS FILE SHOULD NOT EXIST, EVIL SQL SHOULD BE ENCAPSULATED IN EvilQueries,
-
#throwing all of this stuff in user violates demeter like WHOA
-
module User::Querying
-
def find_visible_shareable_by_id(klass, id, opts={} )
-
key = (opts.delete(:key) || :id)
-
::EvilQuery::VisibleShareableById.new(self, klass, key, id, opts).post!
-
end
-
-
def visible_shareables(klass, opts={})
-
opts = prep_opts(klass, opts)
-
shareable_ids = visible_shareable_ids(klass, opts)
-
klass.where(id: shareable_ids).select("DISTINCT #{klass.table_name}.*")
-
.limit(opts[:limit]).order(opts[:order_with_table])
-
end
-
-
def visible_shareable_ids(klass, opts={})
-
visible_ids_from_sql(klass, prep_opts(klass, opts))
-
end
-
-
def contact_for(person)
-
return nil unless person
-
contact_for_person_id(person.id)
-
end
-
-
def block_for(person)
-
return nil unless person
-
blocks.find_by(person_id: person.id)
-
end
-
-
def aspects_with_shareable(base_class_name_or_class, shareable_id)
-
base_class_name = base_class_name_or_class
-
base_class_name = base_class_name_or_class.base_class.to_s if base_class_name_or_class.is_a?(Class)
-
self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => shareable_id, :shareable_type => base_class_name})
-
end
-
-
def contact_for_person_id(person_id)
-
Contact.includes(person: :profile).find_by(user_id: id, person_id: person_id)
-
end
-
-
# @param [Person] person
-
# @return [Boolean] whether person is a contact of this user
-
def has_contact_for?(person)
-
Contact.exists?(:user_id => self.id, :person_id => person.id)
-
end
-
-
def people_in_aspects(requested_aspects, opts={})
-
allowed_aspects = self.aspects & requested_aspects
-
aspect_ids = allowed_aspects.map(&:id)
-
-
people = Person.in_aspects(aspect_ids)
-
-
if opts[:type] == 'remote'
-
people = people.where(:owner_id => nil)
-
elsif opts[:type] == 'local'
-
people = people.where('people.owner_id IS NOT NULL')
-
end
-
people
-
end
-
-
def aspects_with_person person
-
contact_for(person).aspects
-
end
-
-
def posts_from(person, with_order=true)
-
base_query = Post.from_person_visible_by_user(self, person)
-
return base_query.order("posts.created_at desc") if with_order
-
-
base_query
-
end
-
-
def photos_from(person, opts={})
-
opts = prep_opts(Photo, opts)
-
Photo.from_person_visible_by_user(self, person)
-
.by_max_time(opts[:max_time])
-
.limit(opts[:limit])
-
end
-
-
protected
-
-
# @return [Array<Integer>]
-
def visible_ids_from_sql(klass, opts)
-
opts[:klass] = klass
-
opts[:by_members_of] ||= aspect_ids
-
-
klass.connection.select_values(visible_shareable_sql(opts)).map(&:to_i)
-
end
-
-
def visible_shareable_sql(opts)
-
shareable_from_others = construct_shareable_from_others_query(opts)
-
shareable_from_self = construct_shareable_from_self_query(opts)
-
-
"(#{shareable_from_others.to_sql} LIMIT #{opts[:limit]}) " \
-
"UNION ALL (#{shareable_from_self.to_sql} LIMIT #{opts[:limit]}) " \
-
"ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
-
end
-
-
def construct_shareable_from_others_query(opts)
-
logger.debug "[EVIL-QUERY] user.construct_shareable_from_others_query"
-
-
query = visible_shareables_query(posts_from_aspects_query(opts), opts)
-
-
query = query.where(type: opts[:type]) unless opts[:klass] == Photo
-
-
ugly_select_clause(query, opts)
-
end
-
-
# For PostgreSQL and MySQL/MariaDB we use a different query
-
# see issue: https://github.com/diaspora/diaspora/issues/5014
-
def posts_from_aspects_query(opts)
-
if AppConfig.postgres?
-
opts[:klass].where(author_id: Person.in_aspects(opts[:by_members_of]).select("people.id"))
-
else
-
person_ids = Person.connection.select_values(Person.in_aspects(opts[:by_members_of]).select("people.id").to_sql)
-
opts[:klass].where(author_id: person_ids)
-
end
-
end
-
-
def visible_shareables_query(query, opts)
-
query.with_visibility.where(
-
visible_private_shareables(opts).or(opts[:klass].arel_table[:public].eq(true))
-
)
-
end
-
-
def visible_private_shareables(opts)
-
ShareVisibility.arel_table[:user_id].eq(id)
-
.and(ShareVisibility.arel_table[:shareable_type].eq(opts[:klass].to_s))
-
.and(ShareVisibility.arel_table[:hidden].eq(opts[:hidden]))
-
end
-
-
def construct_shareable_from_self_query(opts)
-
conditions = {author_id: person_id}
-
conditions[:type] = opts[:type] if opts.has_key?(:type)
-
query = opts[:klass].where(conditions)
-
-
unless opts[:all_aspects?]
-
query = query.with_aspects.where(
-
AspectVisibility.arel_table[:aspect_id].in(opts[:by_members_of])
-
.or(opts[:klass].arel_table[:public].eq(true))
-
)
-
end
-
-
ugly_select_clause(query, opts)
-
end
-
-
def ugly_select_clause(query, opts)
-
klass = opts[:klass]
-
table = klass.table_name
-
select_clause = "DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at" % [table, table, table]
-
query.select(select_clause).order(opts[:order_with_table])
-
.where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
-
end
-
-
# @return [Hash]
-
def prep_opts(klass, opts)
-
defaults = {
-
:order => 'created_at DESC',
-
:limit => 15,
-
:hidden => false
-
}
-
defaults[:type] = Stream::Base::TYPES_OF_POST_IN_STREAM if klass == Post
-
opts = defaults.merge(opts)
-
if opts[:limit] == :all
-
opts.delete(:limit)
-
end
-
-
opts[:order_field] = opts[:order].split.first.to_sym
-
opts[:order_with_table] = klass.table_name + '.' + opts[:order]
-
-
opts[:max_time] = Time.at(opts[:max_time]) if opts[:max_time].is_a?(Integer)
-
opts[:max_time] ||= Time.now + 1
-
opts
-
end
-
end
-
# frozen_string_literal: true
-
-
module User::SocialActions
-
def comment!(target, text, opts={})
-
Comment::Generator.new(self, target, text).create!(opts).tap do
-
update_or_create_participation!(target)
-
end
-
end
-
-
def participate!(target, opts={})
-
Participation::Generator.new(self, target).create!(opts)
-
end
-
-
def like!(target, opts={})
-
Like::Generator.new(self, target).create!(opts).tap do
-
update_or_create_participation!(target)
-
end
-
end
-
-
def like_comment!(target, opts={})
-
Like::Generator.new(self, target).create!(opts).tap do
-
update_or_create_participation!(target.commentable)
-
end
-
end
-
-
def participate_in_poll!(target, answer, opts={})
-
PollParticipation::Generator.new(self, target, answer).create!(opts).tap do
-
update_or_create_participation!(target)
-
end
-
end
-
-
def reshare!(target, opts={})
-
raise I18n.t("reshares.create.error") if target.author.guid == guid
-
-
build_post(:reshare, :root_guid => target.guid).tap do |reshare|
-
reshare.save!
-
update_or_create_participation!(target)
-
Diaspora::Federation::Dispatcher.defer_dispatch(self, reshare)
-
end
-
end
-
-
def build_conversation(opts={})
-
Conversation.new do |c|
-
c.author = self.person
-
c.subject = opts[:subject]
-
c.participant_ids = [*opts[:participant_ids]] | [self.person_id]
-
c.messages_attributes = [
-
{ author: self.person, text: opts[:message][:text] }
-
]
-
end
-
end
-
-
def build_message(conversation, opts={})
-
conversation.messages.build(
-
text: opts[:text],
-
author: self.person
-
)
-
end
-
-
def update_or_create_participation!(target)
-
return if target.author == person
-
participation = participations.find_by(target_id: target)
-
if participation.present?
-
participation.update!(count: participation.count.next)
-
else
-
participate!(target)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class UserPreference < ApplicationRecord
-
1
belongs_to :user
-
-
1
validate :must_be_valid_email_type
-
-
VALID_EMAIL_TYPES =
-
1
%w[
-
someone_reported
-
mentioned
-
mentioned_in_comment
-
comment_on_post
-
private_message
-
started_sharing
-
also_commented
-
liked
-
liked_comment
-
reshared
-
contacts_birthday
-
].freeze
-
-
1
def must_be_valid_email_type
-
69
unless VALID_EMAIL_TYPES.include?(self.email_type)
-
1
errors.add(:email_type, 'supplied mail type is not a valid or known email type')
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class AspectMembershipPresenter < BasePresenter
-
1
def initialize(membership)
-
87
@membership = membership
-
end
-
-
1
def base_hash
-
{
-
87
id: @membership.id,
-
aspect: AspectPresenter.new(@membership.aspect).as_json,
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class AspectPresenter < BasePresenter
-
1
def initialize(aspect)
-
321
@aspect = aspect
-
end
-
-
1
def as_json
-
310
{ :id => @aspect.id,
-
:name => @aspect.name,
-
}
-
end
-
-
1
def as_api_json(full=false, with_order: true)
-
values = {
-
11
id: @aspect.id,
-
name: @aspect.name
-
}
-
11
values[:order] = @aspect.order_id if with_order
-
11
values
-
end
-
-
1
def to_json(options={})
-
1
as_json.to_json(options)
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
class AvatarPresenter < BasePresenter
-
1
def base_hash(with_default=false)
-
avatar = {
-
115
small: small(with_default),
-
medium: medium(with_default),
-
large: large(with_default),
-
raw: raw(with_default) # rubocop:disable Rails/OutputSafety
-
}.compact
-
-
115
avatar unless avatar.empty?
-
end
-
-
1
def small(with_default=false)
-
115
image_url(size: :thumb_small, fallback_to_default: with_default)
-
end
-
-
1
def medium(with_default=false)
-
446
image_url(size: :thumb_medium, fallback_to_default: with_default)
-
end
-
-
1
def large(with_default=false)
-
115
image_url(size: :scaled_full, fallback_to_default: with_default)
-
end
-
-
1
def raw(with_default=false)
-
# TODO: Replace me with the actual raw photo.
-
115
image_url(fallback_to_default: with_default)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class BasePresenter
-
1
attr_reader :current_user
-
1
include Rails.application.routes.url_helpers
-
-
1
class << self
-
1
def new(*args)
-
2002
return NilPresenter.new if args[0].nil?
-
1922
super *args
-
end
-
-
1
def as_collection(collection, method=:as_json, *args)
-
749
collection.map{|object| self.new(object, *args).send(method) }
-
end
-
end
-
-
1
def initialize(presentable, curr_user=nil)
-
1509
@presentable = presentable
-
1509
@current_user = curr_user
-
end
-
-
1
def respond_to_missing?(method, include_private=false)
-
71
@presentable.respond_to?(method) || super
-
end
-
-
1
def method_missing(method, *args, **kwargs) # rubocop:disable Lint/MissingSuper
-
4817
@presentable.public_send(method, *args, **kwargs)
-
end
-
-
1
class NilPresenter
-
1
def method_missing(method, *args)
-
nil
-
end
-
end
-
-
1
private
-
-
1
def default_url_options
-
52
{host: AppConfig.pod_uri.host, port: AppConfig.pod_uri.port}
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
class BlockPresenter < BasePresenter
-
1
def base_hash
-
1
{ id: id }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class CommentPresenter < BasePresenter
-
1
def as_json(_opts={})
-
{
-
6
id: id,
-
guid: guid,
-
text: message.plain_text_for_json,
-
author: author.as_api_response(:backbone),
-
created_at: created_at,
-
mentioned_people: mentioned_people.as_api_response(:backbone),
-
interactions: build_interactions_json
-
}
-
end
-
-
1
def as_api_response
-
{
-
8
guid: guid,
-
body: message.plain_text_for_json,
-
author: PersonPresenter.new(author).as_api_json,
-
created_at: created_at,
-
mentioned_people: build_mentioned_people_json,
-
reported: current_user.present? && reports.exists?(user: current_user),
-
interactions: build_interaction_state
-
}
-
end
-
-
1
def build_interaction_state
-
{
-
8
liked: current_user.present? && likes.exists?(author: current_user.person),
-
likes_count: likes_count
-
}
-
end
-
-
1
def build_interactions_json
-
{
-
6
likes: as_api(own_likes(likes)),
-
likes_count: likes_count
-
}
-
end
-
-
1
def build_mentioned_people_json
-
9
mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
-
end
-
-
# TODO: Only send the own_like boolean.
-
# Frontend uses the same methods for post-likes as for comment-likes
-
# Whenever the frontend will be refactored, just send the own_like boolean, instead of a full list of likes
-
# The list of likes is already send when API requests the full list.
-
1
def own_likes(likes)
-
6
if current_user
-
5
likes.where(author: current_user.person)
-
else
-
1
likes.none
-
end
-
end
-
-
1
def as_api(collection)
-
6
collection.includes(author: :profile).map {|element|
-
element.as_api_response(:backbone)
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ContactPresenter < BasePresenter
-
1
def base_hash
-
{
-
112
id: id,
-
person_id: person_id
-
}
-
end
-
-
1
def full_hash
-
111
base_hash.merge(
-
76
aspect_memberships: aspect_memberships.map{ |membership| AspectMembershipPresenter.new(membership).base_hash }
-
)
-
end
-
-
1
def full_hash_with_person
-
43
full_hash.merge(person: person_without_contact)
-
end
-
-
1
def as_api_json_without_contact
-
2
PersonPresenter.new(person, current_user).as_api_json
-
end
-
-
1
private
-
-
1
def person_without_contact
-
43
PersonPresenter.new(person, current_user).as_json.except!(:contact)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ConversationPresenter < BasePresenter
-
1
def as_api_json
-
56
read = @presentable.conversation_visibilities.find_by(person_id: current_user.person_id)&.unread == 0
-
{
-
56
guid: @presentable.guid,
-
subject: @presentable.subject,
-
created_at: @presentable.created_at,
-
read: read,
-
111
participants: @presentable.participants.map {|x| PersonPresenter.new(x).as_api_json }
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class LastThreeCommentsDecorator
-
1
def initialize(presenter)
-
32
@presenter = presenter
-
end
-
-
1
def as_json(_options={})
-
32
current_user = @presenter.current_user
-
32
@presenter.as_json.tap do |post|
-
32
post[:interactions].merge!(comments: CommentPresenter.as_collection(
-
@presenter.post.last_three_comments,
-
:as_json,
-
current_user
-
))
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class LikesPresenter < BasePresenter
-
1
def as_api_json
-
{
-
8
guid: @presentable.guid,
-
author: PersonPresenter.new(@presentable.author).as_api_json
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class MessagePresenter < BasePresenter
-
1
def as_api_json
-
{
-
4
guid: @presentable.guid,
-
created_at: @presentable.created_at,
-
body: @presentable.text,
-
author: PersonPresenter.new(@presentable.author).as_api_json
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class NodeInfoPresenter
-
1
delegate :as_json, :content_type, to: :document
-
-
1
def initialize(version)
-
18
@version = version
-
end
-
-
1
def document
-
22
@document ||= NodeInfo.build do |doc|
-
15
doc.version = @version
-
-
15
add_static_data doc
-
15
add_configuration doc
-
15
add_user_counts doc.usage.users
-
15
add_usage doc.usage
-
end
-
end
-
-
1
def add_configuration(doc)
-
15
doc.software.version = version
-
15
doc.services.outbound = available_services
-
15
doc.open_registrations = open_registrations?
-
15
doc.metadata["nodeName"] = name
-
15
doc.metadata["camo"] = camo_config
-
15
doc.metadata["adminAccount"] = admin_account
-
end
-
-
1
def add_static_data(doc)
-
15
doc.software.name = "diaspora"
-
15
doc.software.repository = "https://github.com/diaspora/diaspora"
-
15
doc.software.homepage = "https://diasporafoundation.org/"
-
15
doc.protocols.protocols << "diaspora"
-
end
-
-
1
def add_user_counts(doc)
-
15
return unless expose_user_counts?
-
-
1
doc.total = total_users
-
1
doc.active_halfyear = halfyear_users
-
1
doc.active_month = monthly_users
-
end
-
-
1
def add_usage(doc)
-
15
doc.local_posts = local_posts if expose_posts_counts?
-
15
doc.local_comments = local_comments if expose_comment_counts?
-
end
-
-
1
def expose_user_counts?
-
17
AppConfig.privacy.statistics.user_counts?
-
end
-
-
1
def expose_posts_counts?
-
17
AppConfig.privacy.statistics.post_counts?
-
end
-
-
1
def expose_comment_counts?
-
17
AppConfig.privacy.statistics.comment_counts?
-
end
-
-
1
def name
-
17
AppConfig.settings.pod_name
-
end
-
-
1
def version
-
17
AppConfig.version_string
-
end
-
-
1
def open_registrations?
-
20
AppConfig.settings.enable_registrations?
-
end
-
-
1
def camo_config
-
{
-
15
markdown: AppConfig.privacy.camo.proxy_markdown_images?,
-
opengraph: AppConfig.privacy.camo.proxy_opengraph_thumbnails?,
-
remotePods: AppConfig.privacy.camo.proxy_remote_pod_images?
-
}
-
end
-
-
1
def admin_account
-
15
AppConfig.admins.account if AppConfig.admins.account?
-
end
-
-
1
def available_services
-
27
Configuration::KNOWN_SERVICES.select {|service|
-
81
AppConfig.show_service?(service, nil)
-
}.map(&:to_s)
-
end
-
-
1
def total_users
-
1
@total_users ||= User.active.count
-
end
-
-
1
def monthly_users
-
1
@monthly_users ||= User.monthly_actives.count
-
end
-
-
1
def halfyear_users
-
1
@halfyear_users ||= User.halfyear_actives.count
-
end
-
-
1
def local_posts
-
2
Rails.cache.fetch("NodeInfoPresenter/local_posts", expires_in: 1.hour) do
-
2
@local_posts ||= Post.where(type: "StatusMessage")
-
.joins(:author)
-
.where.not(people: {owner_id: nil})
-
.count
-
end
-
end
-
-
1
def local_comments
-
2
Rails.cache.fetch("NodeInfoPresenter/local_comments", expires_in: 1.hour) do
-
2
@local_comments ||= Comment.joins(:author)
-
.where.not(people: {owner_id: nil})
-
.count
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class NotificationPresenter < BasePresenter
-
1
def as_api_json
-
16
data = base_hash
-
16
data = data.merge(target: target_json) if target
-
16
data
-
end
-
-
1
private
-
-
1
def base_hash
-
{
-
16
guid: guid,
-
type: type_as_json,
-
read: !unread,
-
created_at: created_at,
-
event_creators: creators_json
-
}
-
end
-
-
1
def target_json
-
14
json = {guid: target.guid}
-
14
json[:author] = PersonPresenter.new(target.author).as_api_json if target.author
-
14
json
-
end
-
-
1
def creators_json
-
32
actors.map {|actor| PersonPresenter.new(actor).as_api_json }
-
end
-
-
1
def type_as_json
-
16
NotificationService::NOTIFICATIONS_REVERSE_JSON_TYPES[type]
-
end
-
-
1
def target
-
58
return linked_object if linked_object&.is_a?(Post)
-
26
return linked_object.post if linked_object&.respond_to?(:post)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class OEmbedPresenter
-
1
include PostsHelper
-
-
1
def initialize(post, opts = {})
-
7
@post = post
-
7
@opts = opts
-
end
-
-
1
def to_json(opts={})
-
1
as_json(opts).to_json
-
end
-
-
1
def as_json(opts={})
-
{
-
3
:provider_name => "Diaspora",
-
:provider_url => AppConfig.pod_uri.to_s,
-
:type => 'rich',
-
:version => '1.0',
-
:title => post_title,
-
:author_name => post_author,
-
:author_url => post_author_url,
-
:width => @opts.fetch(:maxwidth, 516),
-
:height => @opts.fetch(:maxheight, 320),
-
:html => iframe_html
-
}
-
end
-
-
1
def self.id_from_url(url)
-
4
URI.parse(url).path.gsub(%r{\/posts\/|\/p\/}, '')
-
end
-
-
1
def post_title
-
3
post_page_title(@post)
-
end
-
-
1
def post_author
-
3
@post.author_name
-
end
-
-
1
def post_author_url
-
3
AppConfig.url_to(Rails.application.routes.url_helpers.person_path(@post.author))
-
end
-
-
1
def iframe_html
-
4
post_iframe_url(@post.id, :height => @opts[:maxheight], :width => @opts[:maxwidth])
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PersonPresenter < BasePresenter
-
1
def base_hash
-
{
-
122
id: id,
-
guid: guid,
-
name: name,
-
diaspora_id: diaspora_handle
-
}
-
end
-
-
1
def as_api_json
-
{
-
321
guid: guid,
-
diaspora_id: diaspora_handle,
-
name: name,
-
avatar: AvatarPresenter.new(@presentable).medium
-
}.compact
-
end
-
-
1
def full_hash
-
122
base_hash_with_contact.merge(
-
relationship: relationship,
-
122
block: is_blocked? ? BlockPresenter.new(current_user_person_block).base_hash : false,
-
is_own_profile: own_profile?,
-
show_profile_info: public_details? || own_profile? || person_is_following_current_user
-
)
-
end
-
-
1
def profile_hash_as_api_json
-
13
if own_profile?
-
6
ProfilePresenter.new(profile).as_self_api_json.merge(guid: guid)
-
else
-
7
show_detailed = @presentable.public_details? || person_is_following_current_user
-
7
ProfilePresenter.new(profile).as_other_api_json(show_detailed).merge(
-
guid: guid,
-
blocked: is_blocked?,
-
relationship: relationship_detailed,
-
aspects: aspects_detailed
-
)
-
end
-
end
-
-
1
def as_json(_options={})
-
99
full_hash_with_profile
-
end
-
-
1
def hovercard
-
9
full_hash.merge(profile: ProfilePresenter.new(profile).for_hovercard)
-
end
-
-
1
def metas_attributes
-
{
-
35
keywords: {name: "keywords", content: comma_separated_tags},
-
description: {name: "description", content: description},
-
og_title: {property: "og:title", content: title},
-
og_description: {property: "og:title", content: description},
-
og_url: {property: "og:url", content: url},
-
og_image: {property: "og:image", content: image_url},
-
og_type: {property: "og:type", content: "profile"},
-
og_profile_username: {property: "og:profile:username", content: name},
-
og_profile_firstname: {property: "og:profile:first_name", content: first_name},
-
og_profile_lastname: {property: "og:profile:last_name", content: last_name}
-
}
-
end
-
-
1
def self.people_names(people)
-
people.map(&:name).join(", ")
-
end
-
-
1
protected
-
-
1
def own_profile?
-
375
current_user.try(:person) == @presentable
-
end
-
-
1
def relationship
-
122
return false unless current_user
-
-
108
contact = current_user_person_contact
-
108
return :not_sharing unless contact
-
-
67
%i(mutual sharing receiving).find do |status|
-
113
contact.public_send("#{status}?")
-
end || :not_sharing
-
end
-
-
1
def relationship_detailed
-
7
status = {receiving: false, sharing: false}
-
7
return status unless current_user
-
-
7
contact = current_user_person_contact
-
7
return status unless contact
-
-
3
status.keys.each do |key|
-
6
status[key] = contact.public_send("#{key}?")
-
end
-
3
status
-
end
-
-
1
def aspects_detailed
-
7
return [] unless current_user_person_contact
-
-
3
aspects_for_person = current_user.aspects_with_person(@presentable)
-
4
aspects_for_person.map {|a| AspectPresenter.new(a).as_api_json(false, with_order: false) }
-
end
-
-
1
def person_is_following_current_user
-
115
return false unless current_user
-
102
contact = current_user_person_contact
-
102
contact && contact.sharing?
-
end
-
-
1
def base_hash_with_contact
-
122
base_hash.merge(
-
122
contact: (!own_profile? && has_contact?) ? contact_hash : false
-
)
-
end
-
-
1
def full_hash_with_profile
-
99
attrs = full_hash
-
-
99
if attrs[:show_profile_info]
-
47
attrs.merge!(profile: ProfilePresenter.new(profile).private_hash)
-
else
-
52
attrs.merge!(profile: ProfilePresenter.new(profile).public_hash)
-
end
-
-
99
attrs
-
end
-
-
1
def contact_hash
-
67
ContactPresenter.new(current_user_person_contact).full_hash
-
end
-
-
1
private
-
-
1
def current_user_person_block
-
130
@block ||= (current_user ? current_user.block_for(@presentable) : Block.none)
-
end
-
-
1
def current_user_person_contact
-
406
@contact ||= (current_user ? current_user.contact_for(@presentable) : Contact.none)
-
end
-
-
1
def has_contact?
-
115
current_user_person_contact.present?
-
end
-
-
1
def is_blocked?
-
129
current_user_person_block.present?
-
end
-
-
1
def title
-
36
name
-
end
-
-
1
def comma_separated_tags
-
36
profile.tags.map(&:name).join(", ") if profile.tags
-
end
-
-
1
def url
-
36
url_for(@presentable)
-
end
-
-
1
def description
-
70
public_details? ? bio : ""
-
end
-
-
1
def image_url
-
36
return AppConfig.url_to @presentable.image_url if @presentable.image_url[0] == "/"
-
@presentable.image_url
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PhotoPresenter < BasePresenter
-
1
def base_hash
-
{
-
id: id,
-
guid: guid,
-
dimensions: {
-
height: height,
-
width: width
-
},
-
sizes: {
-
small: url(:thumb_small),
-
medium: url(:thumb_medium),
-
large: url(:scaled_full),
-
raw: url
-
}
-
}
-
end
-
-
1
def as_api_json(full=false)
-
api_json = {
-
33
dimensions: {
-
height: height,
-
width: width
-
},
-
sizes: {
-
small: url(:thumb_small),
-
medium: url(:thumb_medium),
-
large: url(:scaled_full),
-
raw: url
-
}
-
}
-
-
33
api_json[:guid] = guid if full
-
33
api_json[:created_at] = created_at if full
-
33
api_json[:post] = status_message_guid if full && status_message_guid
-
33
api_json
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PodPresenter < BasePresenter
-
1
def base_hash(*_arg)
-
{
-
12
id: id,
-
host: host,
-
port: port,
-
ssl: ssl,
-
status: status,
-
checked_at: checked_at,
-
response_time: response_time,
-
active: active?,
-
offline: offline?,
-
offline_since: offline_since,
-
created_at: created_at,
-
software: software,
-
error: error
-
}
-
end
-
-
1
alias_method :as_json, :base_hash
-
end
-
# frozen_string_literal: true
-
-
1
class PollPresenter < BasePresenter
-
1
def initialize(poll, current_user=nil)
-
7
super(poll, current_user)
-
-
7
@participation = participation_answer(current_user) if current_user
-
end
-
-
1
def as_api_json
-
{
-
11
guid: guid,
-
participation_count: participation_count,
-
question: question,
-
already_participated: @participation.present?,
-
poll_answers: answers_collection_as_api_json(poll_answers)
-
}
-
end
-
-
1
private
-
-
1
def answers_collection_as_api_json(answers_collection)
-
35
answers_collection.map {|answer| answer_as_api_json(answer) }
-
end
-
-
1
def answer_as_api_json(answer)
-
base = {
-
24
id: answer.id,
-
answer: answer.answer,
-
vote_count: answer.vote_count
-
}
-
24
base[:own_answer] = @participation.try(:poll_answer_id) == answer.id if current_user
-
24
base
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PostInteractionPresenter
-
1
def initialize(post, current_user)
-
90
@post = post
-
90
@current_user = current_user
-
end
-
-
1
def as_json(_options={})
-
{
-
12
likes: as_api(@post.likes),
-
reshares: PostPresenter.as_collection(@post.reshares, :as_json, @current_user),
-
comments: CommentPresenter.as_collection(@post.comments.order("created_at ASC")),
-
participations: as_api(participations),
-
comments_count: @post.comments_count,
-
likes_count: @post.likes_count,
-
reshares_count: @post.reshares_count
-
}
-
end
-
-
1
def as_counters
-
{
-
78
comments: @post.comments_count,
-
likes: @post.likes_count,
-
reshares: @post.reshares_count
-
}
-
end
-
-
1
private
-
-
1
def participations
-
12
return @post.participations.none unless @current_user
-
-
8
@post.participations.where(author: @current_user.person)
-
end
-
-
1
def as_api(collection)
-
24
collection.includes(author: :profile).map {|element|
-
7
element.as_api_response(:backbone)
-
}
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
class PostPresenter < BasePresenter
-
1
include PostsHelper
-
1
include MetaDataHelper
-
-
1
attr_accessor :post
-
-
1
def initialize(presentable, current_user=nil)
-
188
@post = presentable
-
188
super
-
end
-
-
1
def as_json(_options={})
-
86
@post.as_json(only: directly_retrieved_attributes)
-
.merge(non_directly_retrieved_attributes)
-
end
-
-
1
def as_api_response # rubocop:disable Metrics/AbcSize
-
{
-
78
guid: @post.guid,
-
body: build_text,
-
title: title,
-
post_type: @post.post_type,
-
public: @post.public,
-
created_at: @post.created_at,
-
nsfw: @post.nsfw,
-
author: PersonPresenter.new(@post.author).as_api_json,
-
provider_display_name: @post.provider_display_name,
-
interaction_counters: PostInteractionPresenter.new(@post, current_user).as_counters,
-
location: location_as_api_json,
-
poll: PollPresenter.new(@post.poll, current_user).as_api_json,
-
mentioned_people: build_mentioned_people_json,
-
photos: build_photos_json,
-
root: root_api_response,
-
own_interaction_state: build_own_interaction_state,
-
open_graph_object: open_graph_object_api_response,
-
oembed: @post.o_embed_cache.try(:data)
-
}.compact
-
end
-
-
1
def with_interactions
-
7
interactions = PostInteractionPresenter.new(@post, current_user)
-
7
as_json.merge!(interactions: interactions.as_json)
-
end
-
-
1
def with_initial_interactions
-
8
as_json.tap do |post|
-
8
post[:interactions].merge!(
-
likes: LikeService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone),
-
reshares: ReshareService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone)
-
)
-
end
-
end
-
-
1
def metas_attributes
-
{
-
6
keywords: {name: "keywords", content: comma_separated_tags},
-
description: {name: "description", content: description},
-
og_url: {property: "og:url", content: url},
-
og_title: {property: "og:title", content: title},
-
og_image: {property: "og:image", content: images},
-
og_description: {property: "og:description", content: description},
-
og_article_tag: {property: "og:article:tag", content: tags},
-
og_article_author: {property: "og:article:author", content: author_name},
-
og_article_modified: {property: "og:article:modified_time", content: modified_time_iso8601},
-
og_article_published: {property: "og:article:published_time", content: published_time_iso8601}
-
}
-
end
-
-
1
def page_title
-
6
post_page_title @post
-
end
-
-
1
private
-
-
1
def directly_retrieved_attributes
-
86
%i(id guid public created_at interacted_at provider_display_name)
-
end
-
-
1
def non_directly_retrieved_attributes
-
{
-
86
text: build_text,
-
post_type: @post.post_type,
-
nsfw: @post.nsfw,
-
author: @post.author.as_api_response(:backbone),
-
o_embed_cache: @post.o_embed_cache.try(:as_api_response, :backbone),
-
open_graph_cache: build_open_graph_cache,
-
mentioned_people: build_mentioned_people_json,
-
photos: build_photos_json,
-
root: root,
-
title: title,
-
location: @post.post_location,
-
poll: @post.poll,
-
poll_participation_answer_id: poll_participation_answer_id,
-
participation: participates?,
-
interactions: build_interactions_json
-
}
-
end
-
-
1
def title
-
173
@post.message.present? ? @post.message.title : I18n.t("posts.presenter.title", name: @post.author_name)
-
end
-
-
1
def build_text
-
164
if @post.message
-
164
@post.message.plain_text_for_json
-
else
-
@post.text
-
end
-
end
-
-
1
def build_open_graph_cache
-
88
@post.open_graph_cache.try(:as_api_response, :backbone)
-
end
-
-
1
def open_graph_object_api_response
-
78
cache = @post.open_graph_cache
-
78
return unless cache
-
-
{
-
2
type: cache.ob_type,
-
url: cache.url,
-
title: cache.title,
-
image: cache.image,
-
description: cache.description,
-
video_url: cache.video_url
-
}
-
end
-
-
1
def build_mentioned_people_json
-
222
@post.mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
-
end
-
-
1
def build_photos_json
-
176
@post.photos.map {|p| PhotoPresenter.new(p).as_api_json }
-
end
-
-
1
def root
-
88
if @post.respond_to?(:absolute_root) && @post.absolute_root.present?
-
17
PostPresenter.new(@post.absolute_root, current_user).as_json
-
end
-
end
-
-
1
def root_api_response
-
78
is_root_post_exist = @post.respond_to?(:absolute_root) && @post.absolute_root.present?
-
78
PostPresenter.new(@post.absolute_root, current_user).as_api_response if is_root_post_exist
-
end
-
-
1
def build_interactions_json
-
{
-
86
likes: [user_like].compact,
-
reshares: [user_reshare].compact,
-
comments_count: @post.comments_count,
-
likes_count: @post.likes_count,
-
reshares_count: @post.reshares_count
-
}
-
end
-
-
1
def build_own_interaction_state
-
78
if current_user
-
{
-
68
liked: @post.likes.where(author: current_user.person).exists?,
-
reshared: @post.reshares.where(author: current_user.person).exists?,
-
subscribed: participates?,
-
reported: @post.reports.where(user: current_user).exists?
-
}
-
else
-
10
{
-
liked: false,
-
reshared: false,
-
subscribed: false,
-
reported: false
-
}
-
end
-
end
-
-
1
def user_like
-
88
@post.like_for(current_user).try(:as_api_response, :backbone)
-
end
-
-
1
def user_reshare
-
88
@post.reshare_for(current_user).try(:as_api_response, :backbone)
-
end
-
-
1
def poll_participation_answer_id
-
86
@post.poll&.participation_answer(current_user)&.poll_answer_id if user_signed_in?
-
end
-
-
1
def participates?
-
154
user_signed_in? && current_user.participations.where(target_id: @post).exists?
-
end
-
-
1
def user_signed_in?
-
240
current_user.present?
-
end
-
-
1
def person
-
current_user.person
-
end
-
-
1
def images
-
6
photos.any? ? photos.map(&:url) : default_image_url
-
end
-
-
1
def published_time_iso8601
-
7
created_at.to_time.iso8601
-
end
-
-
1
def modified_time_iso8601
-
7
updated_at.to_time.iso8601
-
end
-
-
1
def tags
-
16
tags = @post.is_a?(Reshare) ? @post.absolute_root.try(:tags) : @post.tags
-
16
tags ? tags.map(&:name) : []
-
end
-
-
1
def comma_separated_tags
-
7
tags.join(", ")
-
end
-
-
1
def url
-
7
post_url @post
-
end
-
-
1
def description
-
16
message.try(:plain_text_without_markdown, truncate: 1000)
-
end
-
-
1
def location_as_api_json
-
78
location = @post.post_location
-
78
return if location.values.all?(&:nil?)
-
-
2
location[:lat] = location[:lat].to_f
-
2
location[:lng] = location[:lng].to_f
-
2
location
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ProfilePresenter < BasePresenter
-
1
include PeopleHelper
-
-
1
def base_hash
-
{
-
99
id: id,
-
searchable: searchable
-
}
-
end
-
-
1
def public_hash
-
99
base_hash.merge(
-
avatar: AvatarPresenter.new(@presentable).base_hash(true),
-
tags: tags.pluck(:name)
-
)
-
end
-
-
1
def for_hovercard
-
{
-
10
avatar: AvatarPresenter.new(@presentable).medium,
-
tags: tags.pluck(:name)
-
}
-
end
-
-
1
def as_self_api_json
-
6
base_api_json.merge(added_details_api_json).merge(
-
searchable: searchable,
-
show_profile_info: public_details,
-
nsfw: nsfw
-
)
-
end
-
-
1
def as_other_api_json(all_details)
-
7
return base_api_json unless all_details
-
-
5
base_api_json.merge(added_details_api_json)
-
end
-
-
1
def private_hash
-
47
public_hash.merge(
-
bio: bio_message.plain_text_for_json,
-
birthday: formatted_birthday,
-
gender: gender,
-
location: location_message.plain_text_for_json
-
)
-
end
-
-
1
def formatted_birthday
-
47
birthday_format(birthday) if birthday
-
end
-
-
1
private
-
-
1
def base_api_json
-
{
-
13
name: [first_name, last_name].compact.join(" ").presence,
-
diaspora_id: diaspora_handle,
-
avatar: AvatarPresenter.new(@presentable).base_hash,
-
tags: tags.pluck(:name)
-
}.compact
-
end
-
-
1
def added_details_api_json
-
{
-
11
birthday: birthday.try(:iso8601),
-
gender: gender,
-
location: location_message.plain_text_for_json,
-
bio: bio_message.plain_text_for_json
-
}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ServicePresenter < BasePresenter
-
1
def initialize(service)
-
5
@service = service
-
end
-
-
1
def as_json
-
{
-
5
:provider => @service.provider
-
}
-
end
-
-
1
def to_json(options = {})
-
as_json.to_json(options)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class TagStreamPresenter < BasePresenter
-
1
def title
-
9
@presentable.display_tag_name
-
end
-
-
1
def metas_attributes
-
{
-
9
keywords: {name: "keywords", content: tag_name},
-
description: {name: "description", content: description},
-
og_url: {property: "og:url", content: url},
-
og_title: {property: "og:title", content: title},
-
og_description: {property: "og:description", content: description}
-
}
-
end
-
-
1
private
-
-
1
def description
-
18
I18n.t("streams.tags.title", tags: tag_name)
-
end
-
-
1
def url
-
9
tag_url tag_name
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class UserApplicationPresenter
-
1
attr_reader :scopes
-
-
1
def initialize(application, scopes, authorization_id=nil)
-
24
@app = application
-
24
@scopes = scopes
-
24
@authorization_id = authorization_id
-
end
-
-
1
def id
-
1
@authorization_id
-
end
-
-
1
def name
-
24
@app.client_name
-
end
-
-
1
def image
-
24
@app.image_uri
-
end
-
-
1
def terms_of_services
-
@app.tos_uri
-
end
-
-
1
def policy
-
@app.policy_uri
-
end
-
-
1
def name?
-
24
@app.client_name.present?
-
end
-
-
1
def terms_of_services?
-
48
@app.tos_uri.present?
-
end
-
-
1
def policy?
-
72
@app.policy_uri.present?
-
end
-
-
1
def url
-
48
client_redirect = URI(@app.redirect_uris[0])
-
48
client_redirect.path = "/"
-
48
client_redirect.to_s
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class UserApplicationsPresenter
-
1
def initialize(user)
-
1
@user = user
-
end
-
-
1
def user_applications
-
2
@applications ||= @user.o_auth_applications.map do |app|
-
1
authorization = Api::OpenidConnect::Authorization.find_by_client_id_and_user(app.client_id, @user)
-
1
UserApplicationPresenter.new app, authorization.scopes, authorization.id
-
end
-
end
-
-
1
def applications_count
-
1
user_applications.size
-
end
-
-
1
def applications?
-
1
applications_count > 0
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class UserPresenter
-
1
attr_accessor :user, :aspects_ids
-
-
1
def initialize(user, aspects_ids)
-
625
self.user = user
-
625
self.aspects_ids = aspects_ids
-
end
-
-
1
def to_json(options={})
-
219
user.person.as_api_response(:backbone).update(
-
notifications_count: notifications_count,
-
unread_messages_count: unread_messages_count,
-
admin: admin,
-
moderator: moderator,
-
aspects: aspects,
-
services: services,
-
following_count: user.contacts.receiving.count,
-
configured_services: configured_services
-
).to_json(options)
-
end
-
-
1
def services
-
220
ServicePresenter.as_collection(user.services)
-
end
-
-
1
def configured_services
-
220
user.services.map(&:provider)
-
end
-
-
1
def aspects
-
229
@aspects ||= begin
-
220
aspects = AspectPresenter.as_collection(user.aspects)
-
220
no_aspects = aspects_ids.empty?
-
441
aspects.each {|a| a[:selected] = no_aspects || aspects_ids.include?(a[:id].to_s) }
-
end
-
end
-
-
1
def notifications_count
-
219
@notification_count ||= user.unread_notifications.count
-
end
-
-
1
def unread_messages_count
-
219
@unread_message_count ||= user.unread_message_count
-
end
-
-
1
def admin
-
219
user.admin?
-
end
-
-
1
def moderator
-
219
user.moderator?
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
1
class AspectSerializer < ActiveModel::Serializer
-
1
attributes :name
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
1
class ContactSerializer < ActiveModel::Serializer
-
1
attributes :sharing,
-
:receiving,
-
:following,
-
:followed,
-
:person_guid,
-
:person_name,
-
:account_id,
-
:public_key
-
-
1
has_many :contact_groups_membership
-
-
1
def following
-
19
object.sharing
-
end
-
-
1
def followed
-
19
object.receiving
-
end
-
-
1
def account_id
-
19
object.person_diaspora_handle
-
end
-
-
1
def contact_groups_membership
-
19
object.aspects.map(&:name)
-
end
-
-
1
def public_key
-
19
object.person.serialized_public_key
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
1
class OthersDataSerializer < ActiveModel::Serializer
-
# Relayables of other people in the archive: comments, likes, participations, poll participations where author is
-
# the archive owner
-
1
has_many :relayables, serializer: FlatMapArraySerializer, each_serializer: FederationEntitySerializer
-
-
1
def initialize(user_id)
-
29
@user_id = user_id
-
29
super(object)
-
end
-
-
1
private
-
-
1
def object
-
57
User.find(@user_id)
-
end
-
-
1
def relayables
-
28
%i[comments likes poll_participations].map {|relayable|
-
84
others_relayables.send(relayable).find_each(batch_size: 20)
-
}
-
end
-
-
1
def others_relayables
-
84
@others_relayables ||= Diaspora::Exporter::OthersRelayables.new(object.person_id)
-
end
-
-
# Avoid calling pointless #embedded_in_root_associations method
-
1
def serializable_data
-
26
{}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
# This is a serializer for the user's own posts
-
1
class OwnPostSerializer < FederationEntitySerializer
-
# Only for public posts.
-
# Includes URIs of pods which must be notified on the post updates.
-
# Must always include local pod URI since we will want all the updates on the post if user migrates.
-
1
has_many :subscribed_pods_uris
-
-
# Only for private posts.
-
# Includes diaspora* IDs of people who must be notified on post updates.
-
1
has_many :subscribed_users_ids
-
-
# Normally accepts Post as an object.
-
1
def initialize(*)
-
25
super
-
25
self.except = [excluded_subscription_key]
-
end
-
-
1
private
-
-
1
def subscribed_pods_uris
-
8
object.subscribed_pods_uris.push(AppConfig.pod_uri.to_s)
-
end
-
-
1
def subscribed_users_ids
-
17
object.subscribers.map(&:diaspora_handle)
-
end
-
-
1
def excluded_subscription_key
-
25
object.public? ? :subscribed_users_ids : :subscribed_pods_uris
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
# This is a serializer for the user's own relayables. We remove signature from the own relayables since it isn't
-
# useful and takes space.
-
1
class OwnRelayablesSerializer < FederationEntitySerializer
-
1
private
-
-
1
def modify_serializable_object(hash)
-
10
super.tap {|hash|
-
10
hash[:entity_data].delete(:author_signature)
-
}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Export
-
1
class UserSerializer < ActiveModel::Serializer
-
1
attributes :username,
-
:email,
-
:language,
-
:private_key,
-
:disable_mail,
-
:show_community_spotlight_in_stream,
-
:auto_follow_back,
-
:auto_follow_back_aspect,
-
:blocks
-
1
has_one :profile, serializer: FederationEntitySerializer
-
1
has_many :contact_groups, each_serializer: Export::AspectSerializer
-
1
has_many :contacts, each_serializer: Export::ContactSerializer
-
1
has_many :posts, each_serializer: Export::OwnPostSerializer
-
1
has_many :followed_tags
-
1
has_many :post_subscriptions
-
-
1
has_many :relayables, serializer: FlatMapArraySerializer, each_serializer: Export::OwnRelayablesSerializer
-
-
1
def initialize(user_id, options={})
-
35
@user_id = user_id
-
35
super(object, options)
-
end
-
-
1
private
-
-
1
def object
-
575
User.find(@user_id)
-
end
-
-
1
def posts
-
33
object.posts.find_each(batch_size: 20)
-
end
-
-
1
def contacts
-
33
object.contacts.find_each(batch_size: 100)
-
end
-
-
1
def relayables
-
33
[comments, likes, poll_participations].map {|relayable|
-
99
relayable.find_each(batch_size: 20)
-
}
-
end
-
-
1
def blocks
-
27
object.blocks.map(&:person_diaspora_handle)
-
end
-
-
1
%i[comments likes poll_participations].each {|collection|
-
3
delegate collection, to: :person
-
}
-
-
1
delegate :person, to: :object
-
-
1
def contact_groups
-
33
object.aspects
-
end
-
-
1
def private_key
-
27
object.serialized_private_key
-
end
-
-
1
def followed_tags
-
33
object.followed_tags.map(&:name)
-
end
-
-
1
def post_subscriptions
-
33
Post.subscribed_by(object).pluck(:guid)
-
end
-
-
# Avoid calling pointless #embedded_in_root_associations method
-
1
def serializable_data
-
26
{}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# This is an ActiveModel::Serializer based class which uses DiasporaFederation::Entity JSON serialization
-
# features in order to serialize local DB objects. To determine a type of entity class to use the same routines
-
# are used as for federation messages generation.
-
1
class FederationEntitySerializer < ActiveModel::Serializer
-
1
include SerializerPostProcessing
-
1
include Diaspora::Logging
-
-
1
private
-
-
1
def modify_serializable_object(hash)
-
81
hash.merge(entity.to_json)
-
rescue DiasporaFederation::Entities::Relayable::AuthorPrivateKeyNotFound => e
-
# The author of this relayable probably migrated from this pod to a different pod,
-
# and we neither have the signature nor the new private key to generate a valid signature.
-
# But we can use the private key of the old user to generate the signature it had when this entity was created
-
1
old_person = AccountMigration.joins(:old_person)
-
.where("new_person_id = ? AND people.owner_id IS NOT NULL", object.author_id)
-
.first.old_person
-
1
if old_person
-
1
logger.info "Using private key of #{old_person.diaspora_handle} to export: #{e.message}"
-
1
object.author = old_person
-
1
hash.merge(entity.to_json)
-
else
-
logger.warn "Skip entity for export because #{e.class}: #{e.message}"
-
end
-
end
-
-
1
def entity
-
82
Diaspora::Federation::Entities.build(object)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class FlatMapArraySerializer < ActiveModel::ArraySerializer
-
1
def serializable_object(options={})
-
61
@object.flat_map do |subarray|
-
183
subarray.map do |item|
-
21
serializer_for(item).serializable_object_with_notification(options)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class NotificationSerializer < ActiveModel::Serializer
-
1
attributes :id,
-
:target_type,
-
:target_id,
-
:recipient_id,
-
:unread,
-
:created_at,
-
:updated_at,
-
:note_html
-
-
1
def note_html
-
7
context.render_to_string(partial: "notifications/notification", locals: {note: object, no_aspect_dropdown: true})
-
end
-
-
1
def initialize(*_)
-
7
super
-
7
self.polymorphic = true
-
7
self.root = false
-
end
-
end
-
# frozen_string_literal: true
-
-
# This module encapsulates knowledge about the way AMS works with the serializable object.
-
# The main responsibility of this module is to allow changing resulting object just before the
-
# JSON serialization happens.
-
1
module SerializerPostProcessing
-
# serializable_object output is used in AMS to produce a hash from input object that is passed to JSON serializer.
-
# serializable_object of ActiveModel::Serializer is not documented as officialy available API
-
# NOTE: if we ever move to AMS 0.10, this method was renamed there to serializable_hash
-
1
def serializable_object(options={})
-
83
modify_serializable_object(super)
-
end
-
-
# Users of this module may override this method in order to change serializable_object after
-
# the serializable hash generation and before its serialization.
-
1
def modify_serializable_object(hash)
-
1
hash
-
end
-
-
# except is an array of keys that are excluded from serialized_object before JSON serialization
-
1
attr_accessor :except
-
end
-
# frozen_string_literal: true
-
-
1
class UserInfoSerializer < ActiveModel::Serializer
-
1
attributes :sub, :name, :nickname, :profile, :picture
-
-
1
def sub
-
4
auth = serialization_options[:authorization]
-
4
Api::OpenidConnect::SubjectIdentifierCreator.create(auth)
-
end
-
-
1
def name
-
2
(object.first_name || "") + (object.last_name || "")
-
end
-
-
1
def nickname
-
2
object.name
-
end
-
-
1
def profile
-
2
api_v1_user_url
-
end
-
-
1
def picture
-
2
object.image_url(fallback_to_default: false)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class AspectsMembershipService
-
1
def initialize(user=nil)
-
71
@user = user
-
end
-
-
1
def create(aspect_id, person_id)
-
47
person = Person.find(person_id)
-
46
aspect = @user.aspects.where(id: aspect_id).first
-
46
raise ActiveRecord::RecordNotFound unless person.present? && aspect.present?
-
-
42
contact = @user.share_with(person, aspect)
-
39
raise I18n.t("aspects.add_to_aspect.failure") if contact.blank?
-
-
38
AspectMembership.where(contact_id: contact.id, aspect_id: aspect.id).first
-
end
-
-
1
def destroy_by_ids(aspect_id, contact_id)
-
9
aspect = @user.aspects.where(id: aspect_id).first
-
9
contact = @user.contacts.where(person_id: contact_id).first
-
9
destroy(aspect, contact)
-
end
-
-
1
def destroy_by_membership_id(membership_id)
-
5
aspect = @user.aspects.joins(:aspect_memberships).where(aspect_memberships: {id: membership_id}).first
-
5
contact = @user.contacts.joins(:aspect_memberships).where(aspect_memberships: {id: membership_id}).first
-
5
destroy(aspect, contact)
-
end
-
-
1
def contacts_in_aspect(aspect_id)
-
7
order = [Arel.sql("contact_id IS NOT NULL DESC"), "profiles.first_name ASC", "profiles.last_name ASC",
-
"profiles.diaspora_handle ASC"]
-
7
@user.aspects.find(aspect_id) # to provide better error code if aspect isn't correct
-
4
contacts = @user.contacts.arel_table
-
4
aspect_memberships = AspectMembership.arel_table
-
4
@user.contacts.joins(
-
contacts.join(aspect_memberships).on(
-
aspect_memberships[:aspect_id].eq(aspect_id).and(
-
aspect_memberships[:contact_id].eq(contacts[:id])
-
)
-
).join_sources
-
).includes(person: :profile).order(order)
-
end
-
-
1
def all_contacts
-
3
order = ["profiles.first_name ASC", "profiles.last_name ASC",
-
"profiles.diaspora_handle ASC"]
-
3
@user.contacts.includes(person: :profile).order(order)
-
end
-
-
1
private
-
-
1
def destroy(aspect, contact)
-
14
raise ActiveRecord::RecordNotFound unless aspect.present? && contact.present?
-
-
5
raise Diaspora::NotMine unless @user.mine?(aspect) && @user.mine?(contact)
-
-
5
membership = contact.aspect_memberships.where(aspect_id: aspect.id).first
-
5
raise ActiveRecord::RecordNotFound if membership.blank?
-
-
4
success = membership.destroy
-
-
4
{success: success, membership: membership}
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class BlockService
-
1
def initialize(user)
-
17
@user = user
-
end
-
-
1
def block(person)
-
8
raise ActiveRecord::RecordNotUnique if @user.blocks.exists?(person: person)
-
-
7
block = @user.blocks.create!(person: person)
-
7
contact = @user.contact_for(person)
-
-
7
if contact
-
2
@user.disconnect(contact)
-
5
elsif block.person.remote?
-
1
Diaspora::Federation::Dispatcher.defer_dispatch(@user, block)
-
end
-
end
-
-
1
def unblock(person)
-
2
remove_block(@user.blocks.find_by!(person: person))
-
end
-
-
1
def remove_block(block)
-
7
block.destroy
-
7
ContactRetraction.for(block).defer_dispatch(@user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class CommentService
-
1
def initialize(user=nil)
-
373
@user = user
-
end
-
-
1
def create(post_id, text)
-
273
post = post_service.find!(post_id)
-
270
user.comment!(post, text)
-
end
-
-
1
def find_for_post(post_id)
-
32
post_service.find!(post_id).comments.for_a_stream
-
end
-
-
1
def find!(id_or_guid)
-
71
Comment.find_by!(comment_key(id_or_guid) => id_or_guid)
-
end
-
-
1
def destroy(comment_id)
-
9
comment = Comment.find(comment_id)
-
7
if user.owns?(comment) || user.owns?(comment.parent)
-
5
user.retract(comment)
-
5
true
-
else
-
2
false
-
end
-
end
-
-
1
def destroy!(comment_guid)
-
6
comment = find!(comment_guid)
-
5
if user.owns?(comment)
-
2
user.retract(comment)
-
3
elsif user.owns?(comment.parent)
-
1
user.retract(comment)
-
2
elsif comment
-
2
raise ActiveRecord::RecordInvalid
-
else
-
raise ActiveRecord::RecordNotFound
-
end
-
end
-
-
1
private
-
-
1
attr_reader :user
-
-
# We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
-
1
def comment_key(id_or_guid)
-
71
id_or_guid.to_s.length < 16 ? :id : :guid
-
end
-
-
1
def post_service
-
305
@post_service ||= PostService.new(user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ConversationService
-
1
def initialize(user=nil)
-
110
@user = user
-
end
-
-
1
def all_for_user(filter={})
-
8
conversation_filter = {}
-
8
unless filter[:only_after].nil?
-
conversation_filter = \
-
2
"conversations.created_at >= ?", filter[:only_after]
-
end
-
-
8
visibility_filter = if filter[:unread]
-
{
-
2
person_id: @user.person_id,
-
unread: 1
-
}
-
else
-
6
{person_id: @user.person_id}
-
end
-
-
8
Conversation.where(conversation_filter)
-
.joins(:conversation_visibilities)
-
.where(conversation_visibilities: visibility_filter)
-
.all
-
end
-
-
1
def build(subject, text, recipients)
-
46
person_ids = @user.contacts
-
.mutual
-
.where(person_id: recipients)
-
.pluck(:person_id)
-
-
opts = {
-
46
subject: subject,
-
message: {text: text},
-
participant_ids: person_ids
-
}
-
-
46
@user.build_conversation(opts)
-
end
-
-
1
def find!(conversation_guid)
-
56
conversation = Conversation.find_by!(guid: conversation_guid)
-
47
@user.conversations
-
.joins(:conversation_visibilities)
-
.where(conversation_visibilities: {
-
person_id: @user.person_id,
-
conversation_id: conversation.id
-
}).first!
-
end
-
-
1
def destroy!(conversation_guid)
-
3
conversation = find!(conversation_guid)
-
1
conversation.destroy!
-
end
-
-
1
def get_visibility(conversation_guid)
-
6
conversation = find!(conversation_guid)
-
4
ConversationVisibility.where(
-
person_id: @user.person.id,
-
conversation_id: conversation.id
-
).first!
-
end
-
end
-
# frozen_string_literal: true
-
-
# Encapsulates logic of processing diaspora:// links
-
1
class DiasporaLinkService
-
1
attr_reader :type, :author, :guid
-
-
1
def initialize(link)
-
13
@link = link.dup
-
13
parse
-
end
-
-
1
def find_or_fetch_entity
-
13
if type && guid
-
9
entity_finder.find || fetch_entity
-
4
elsif author
-
2
find_or_fetch_person
-
end
-
end
-
-
1
private
-
-
1
attr_accessor :link
-
-
1
def fetch_entity
-
4
DiasporaFederation::Federation::Fetcher.fetch_public(author, type, guid)
-
1
entity_finder.find
-
rescue DiasporaFederation::Federation::Fetcher::NotFetchable
-
3
nil
-
end
-
-
1
def entity_finder
-
10
@entity_finder ||= Diaspora::EntityFinder.new(type, guid)
-
end
-
-
1
def find_or_fetch_person
-
2
Person.find_or_fetch_by_identifier(author)
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
1
nil
-
end
-
-
1
def normalize
-
13
link.gsub!(%r{^web\+diaspora://}, "diaspora://") ||
-
link.gsub!(%r{^//}, "diaspora://") ||
-
%r{^diaspora://}.match(link) ||
-
self.link = "diaspora://#{link}"
-
end
-
-
1
def parse
-
13
normalize
-
13
match = DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX.match(link)
-
13
if match
-
9
@author, @type, @guid = match.captures
-
else
-
4
@author = %r{^diaspora://(#{Validation::Rule::DiasporaId::DIASPORA_ID_REGEX})$}u.match(link)&.captures&.first
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ImportService
-
1
include Diaspora::Logging
-
-
1
def import_by_user(user, opts={})
-
import_by_files(user.export.current_path, user.exported_photos_file.current_path, user.username, opts)
-
end
-
-
1
def import_by_files(path_to_profile, path_to_photos, username, opts={})
-
2
if path_to_profile.present?
-
logger.info "Import for profile #{username} at path #{path_to_profile} requested"
-
import_user_profile(path_to_profile, username, opts.merge(photo_migration: path_to_photos.present?))
-
end
-
-
2
user = User.find_by(username: username)
-
2
raise ArgumentError, "Username #{username} should exist before uploading photos." if user.nil?
-
-
2
if path_to_photos.present?
-
2
logger.info("Importing photos from import file for '#{username}' from #{path_to_photos}")
-
2
import_user_photos(user, path_to_photos)
-
end
-
2
remove_file_references(user)
-
end
-
-
1
private
-
-
1
def import_user_profile(path_to_profile, username, opts)
-
raise ArgumentError, "Profile file not found at path: #{path_to_profile}" unless File.exist?(path_to_profile)
-
-
service = MigrationService.new(path_to_profile, username, opts)
-
logger.info "Start validating user profile #{username}"
-
service.validate
-
logger.info "Start importing user profile for '#{username}'"
-
service.perform!
-
logger.info "Successfully imported profile: #{username}"
-
rescue MigrationService::ArchiveValidationFailed => e
-
logger.error "Errors in the archive found: #{e.message}"
-
rescue MigrationService::MigrationAlreadyExists
-
logger.error "Migration record already exists for the user, can't continue"
-
rescue MigrationService::SelfMigrationNotAllowed
-
logger.error "You can't migrate onto your own account"
-
end
-
-
1
def import_user_photos(user, path_to_photos)
-
2
raise ArgumentError, "Photos file not found at path: #{path_to_photos}" unless File.exist?(path_to_photos)
-
-
2
uncompressed_photos_folder = unzip_photos_file(path_to_photos)
-
2
user.posts.find_in_batches do |posts|
-
2
import_photos_for_posts(user, posts, uncompressed_photos_folder)
-
end
-
2
FileUtils.rm_r(uncompressed_photos_folder)
-
end
-
-
1
def import_photos_for_posts(user, posts, source_dir)
-
2
posts.each do |post|
-
2
post.photos.each do |photo|
-
2
uploaded_file = "#{source_dir}/#{photo.remote_photo_name}"
-
2
next unless File.exist?(uploaded_file) && photo.remote_photo_name.present?
-
-
# Don't overwrite existing photos if they have the same filename.
-
# Generate a new random filename if a conflict exists and re-federate the photo to update on remote pods.
-
2
random_string = File.basename(uploaded_file, ".*")
-
2
conflicting_photo_exists = Photo.where.not(id: photo.id).exists?(random_string: random_string)
-
2
random_string = SecureRandom.hex(10) if conflicting_photo_exists
-
-
2
store_and_process_photo(photo, uploaded_file, random_string)
-
-
2
Diaspora::Federation::Dispatcher.build(user, photo).dispatch if conflicting_photo_exists
-
end
-
end
-
end
-
-
1
def store_and_process_photo(photo, uploaded_file, random_string)
-
2
File.open(uploaded_file) do |file|
-
2
photo.random_string = random_string
-
2
photo.keep_original_format = true
-
2
photo.unprocessed_image.store! file
-
2
photo.update_remote_path
-
2
photo.save(touch: false)
-
end
-
2
photo.queue_processing_job
-
end
-
-
1
def unzip_photos_file(photo_file_path)
-
2
folder = create_folder(photo_file_path)
-
2
Zip::File.open(photo_file_path) do |zip_file|
-
2
zip_file.each do |file|
-
2
target_name = "#{folder}#{Pathname::SEPARATOR_LIST}#{file}"
-
2
zip_file.extract(file, target_name) unless File.exist?(target_name)
-
rescue Errno::ENOENT => e
-
logger.error e.to_s
-
end
-
end
-
2
folder
-
end
-
-
1
def create_folder(compressed_file_name)
-
2
extension = File.extname(compressed_file_name)
-
2
folder = compressed_file_name.delete_suffix(extension)
-
2
FileUtils.mkdir(folder) unless File.exist?(folder)
-
2
folder
-
end
-
-
1
def remove_file_references(user)
-
2
user.remove_exported_photos_file
-
2
user.remove_export
-
2
user.save
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class LikeService
-
1
def initialize(user=nil)
-
145
@user = user
-
end
-
-
1
def create_for_post(post_id)
-
53
post = post_service.find!(post_id)
-
49
user.like!(post)
-
end
-
-
1
def create_for_comment(comment_id)
-
31
comment = comment_service.find!(comment_id)
-
30
post_service.find!(comment.commentable_id) # checks implicit for visible posts
-
29
user.like_comment!(comment)
-
end
-
-
1
def destroy(like_id)
-
9
like = Like.find(like_id)
-
7
if user.owns?(like)
-
3
user.retract(like)
-
3
true
-
else
-
4
false
-
end
-
end
-
-
1
def find_for_post(post_id)
-
30
likes = post_service.find!(post_id).likes
-
26
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
-
end
-
-
1
def find_for_comment(comment_id)
-
14
comment = comment_service.find!(comment_id)
-
14
post_service.find!(comment.post.id) # checks implicit for visible posts
-
12
likes = comment.likes
-
12
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
-
end
-
-
1
def unlike_post(post_id)
-
4
likes = post_service.find!(post_id).likes
-
4
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
-
4
if !likes.empty? && user.owns?(likes[0])
-
3
user.retract(likes[0])
-
3
true
-
else
-
1
false
-
end
-
end
-
-
1
def unlike_comment(comment_id)
-
4
likes = comment_service.find!(comment_id).likes
-
4
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
-
4
if !likes.empty? && user.owns?(likes[0])
-
3
user.retract(likes[0])
-
3
true
-
else
-
1
false
-
end
-
end
-
-
1
private
-
-
1
attr_reader :user
-
-
1
def post_service
-
131
@post_service ||= PostService.new(user)
-
end
-
-
1
def comment_service
-
49
@comment_service ||= CommentService.new(user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class MigrationService
-
1
attr_reader :archive_path, :new_user_name, :opts
-
-
1
delegate :errors, :warnings, to: :archive_validator
-
-
1
def initialize(archive_path, new_user_name, opts={})
-
10
@archive_path = archive_path
-
10
@new_user_name = new_user_name
-
10
@opts = opts
-
end
-
-
1
def validate
-
3
return unless archive_file_exists?
-
-
3
archive_validator.validate
-
3
raise ArchiveValidationFailed, errors.join("\n") if errors.any?
-
3
raise MigrationAlreadyExists if AccountMigration.where(old_person: old_person).any?
-
2
raise SelfMigrationNotAllowed if self_import?
-
end
-
-
1
def perform!
-
2
find_or_create_user
-
2
import_archive
-
2
run_migration
-
ensure
-
2
remove_intermediate_file
-
end
-
-
1
def self_import?
-
2
source_diaspora_id = archive_validator.archive_author_diaspora_id
-
2
target_diaspora_id = "#{new_user_name}#{User.diaspora_id_host}"
-
2
source_diaspora_id == target_diaspora_id
-
end
-
-
# when old person can't be resolved we still import data but we don't create&perform AccountMigration instance
-
1
def only_import?
-
2
old_person.nil?
-
end
-
-
1
private
-
-
1
def find_or_create_user
-
5
archive_importer.find_or_create_user(username: new_user_name, password: SecureRandom.hex)
-
end
-
-
1
def import_archive
-
2
archive_importer.import(opts)
-
end
-
-
1
def run_migration
-
2
account_migration.save
-
2
account_migration.perform!
-
end
-
-
1
def account_migration
-
7
@account_migration ||= AccountMigration.new(
-
old_person: old_person,
-
new_person: archive_importer.user.person,
-
old_private_key: archive_importer.serialized_private_key,
-
old_person_diaspora_id: archive_importer.archive_author_diaspora_id,
-
archive_contacts: archive_importer.contacts,
-
remote_photo_path: remote_photo_path
-
)
-
end
-
-
1
def old_person
-
10
@old_person ||= Person.by_account_identifier(archive_validator.archive_author_diaspora_id)
-
end
-
-
1
def archive_importer
-
27
@archive_importer ||= ArchiveImporter.new(archive_validator.archive_hash)
-
end
-
-
1
def archive_validator
-
24
@archive_validator ||= ArchiveValidator.new(archive_file)
-
end
-
-
1
def archive_file
-
10
return uncompressed_zip if zip_file?
-
9
return uncompressed_gz if gzip_file?
-
-
8
File.new(archive_path, "r")
-
end
-
-
1
def archive_file_exists?
-
3
File.exist?(archive_path)
-
end
-
-
1
def zip_file?
-
10
filetype = MIME::Types.type_for(archive_path).first.content_type
-
10
filetype.eql?("application/zip")
-
end
-
-
1
def gzip_file?
-
9
filetype = MIME::Types.type_for(archive_path).first.content_type
-
9
filetype.eql?("application/gzip")
-
end
-
-
1
def uncompressed_gz
-
1
target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST
-
1
unzipped_archive_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file
-
-
1
Zlib::GzipReader.open(archive_path) {|gz|
-
1
File.open(unzipped_archive_file, "w") do |output_stream|
-
1
IO.copy_stream(gz, output_stream)
-
end
-
1
@intermediate_file = unzipped_archive_file
-
}
-
1
File.new(unzipped_archive_file, "r")
-
end
-
-
1
def uncompressed_zip
-
1
target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST
-
1
zip_stream = Zip::InputStream.open(archive_path)
-
1
while entry = zip_stream.get_next_entry # rubocop:disable Lint/AssignmentInCondition
-
1
next unless entry.name.end_with?(".json")
-
-
1
target_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file
-
1
entry.extract(target_file)
-
1
@intermediate_file = target_file
-
1
return File.new(target_file, "r")
-
end
-
end
-
-
1
def remove_intermediate_file
-
# If an unzip operation created an unzipped file, remove it after migration
-
2
return if @intermediate_file.nil?
-
return unless File.exist?(@intermediate_file)
-
-
File.delete(@intermediate_file)
-
end
-
-
1
def remote_photo_path
-
5
return unless opts.fetch(:photo_migration, false)
-
-
2
if AppConfig.environment.s3.enable?
-
1
return "https://#{AppConfig.environment.s3.bucket.get}.s3.amazonaws.com/uploads/images/"
-
end
-
-
1
"#{AppConfig.pod_uri}uploads/images/"
-
end
-
-
1
class ArchiveValidationFailed < RuntimeError
-
end
-
-
1
class MigrationAlreadyExists < RuntimeError
-
end
-
-
1
class SelfMigrationNotAllowed < RuntimeError
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class NotificationService
-
NOTIFICATION_TYPES = {
-
1
Comment => [Notifications::MentionedInComment, Notifications::CommentOnPost, Notifications::AlsoCommented],
-
Like => [Notifications::Liked, Notifications::LikedComment],
-
StatusMessage => [Notifications::MentionedInPost],
-
Conversation => [Notifications::PrivateMessage],
-
Message => [Notifications::PrivateMessage],
-
Reshare => [Notifications::Reshared],
-
Contact => [Notifications::StartedSharing]
-
}.freeze
-
-
1
NOTIFICATIONS_JSON_TYPES = {
-
"also_commented" => "Notifications::AlsoCommented",
-
"comment_on_post" => "Notifications::CommentOnPost",
-
"liked" => "Notifications::Liked",
-
"liked_comment" => "Notifications::LikedComment",
-
"mentioned" => "Notifications::MentionedInPost",
-
"mentioned_in_comment" => "Notifications::MentionedInComment",
-
"reshared" => "Notifications::Reshared",
-
"started_sharing" => "Notifications::StartedSharing",
-
"contacts_birthday" => "Notifications::ContactsBirthday"
-
}.freeze
-
-
1
NOTIFICATIONS_REVERSE_JSON_TYPES = NOTIFICATIONS_JSON_TYPES.invert.freeze
-
-
1
def initialize(user=nil)
-
2064
@user = user
-
end
-
-
1
def index(unread_only=nil, only_after=nil)
-
12
query_string = "recipient_id = ? "
-
12
query_string += "AND unread = true " if unread_only
-
12
where_clause = [query_string, @user.id]
-
12
if only_after
-
5
query_string += " AND created_at >= ?"
-
5
where_clause = [query_string, @user.id, only_after]
-
end
-
12
Notification.where(where_clause).includes(:target, actors: :profile)
-
end
-
-
1
def get_by_guid(guid)
-
9
Notification.where(recipient_id: @user.id, guid: guid).first
-
end
-
-
1
def update_status_by_guid(guid, is_read_status)
-
5
notification = get_by_guid(guid)
-
5
raise ActiveRecord::RecordNotFound unless notification
-
-
4
notification.set_read_state(is_read_status)
-
4
true
-
end
-
-
1
def notify(object, recipient_user_ids)
-
3624
notification_types(object).each {|type| type.notify(object, recipient_user_ids) }
-
end
-
-
1
private
-
-
1
def notification_types(object)
-
2046
NOTIFICATION_TYPES.fetch(object.class, [])
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PhotoService
-
1
def initialize(user=nil, deny_raw_files=true)
-
31
@user = user
-
31
@deny_raw_files = deny_raw_files
-
end
-
-
1
def visible_photo(photo_guid)
-
10
Photo.owned_or_visible_by_user(@user).where(guid: photo_guid).first
-
end
-
-
1
def create_from_params_and_file(base_params, uploaded_file)
-
21
photo_params = build_params(base_params)
-
21
raise RuntimeError if @deny_raw_files && !confirm_uploaded_file_settings(uploaded_file)
-
-
20
photo_params[:user_file] = uploaded_file
-
20
photo = @user.build_post(:photo, photo_params)
-
16
raise RuntimeError unless photo.save
-
-
16
send_messages(photo, photo_params)
-
16
update_profile_photo(photo) if photo_params[:set_profile_photo]
-
-
16
photo
-
end
-
-
1
private
-
-
1
def build_params(base_params)
-
21
photo_params = base_params.permit(:pending, :set_profile_photo, aspect_ids: [])
-
21
if base_params.permit(:aspect_ids)[:aspect_ids] == "all"
-
7
photo_params[:aspect_ids] = @user.aspects.map(&:id)
-
14
elsif photo_params[:aspect_ids].is_a?(Hash)
-
photo_params[:aspect_ids] = params[:photo][:aspect_ids].values
-
end
-
21
photo_params
-
end
-
-
1
def confirm_uploaded_file_settings(uploaded_file)
-
13
unless uploaded_file.is_a?(ActionDispatch::Http::UploadedFile) || uploaded_file.is_a?(Rack::Test::UploadedFile)
-
1
return false
-
end
-
12
return false if uploaded_file.original_filename.empty?
-
-
12
return false if uploaded_file.content_type.empty?
-
-
12
true
-
end
-
-
1
def send_messages(photo, photo_params)
-
16
send_to_streams(photo, photo_params) unless photo.pending && photo.public?
-
-
16
@user.dispatch_post(photo, to: photo_params[:aspect_ids]) unless photo.pending
-
end
-
-
1
def update_profile_photo(photo)
-
3
@user.update_profile(photo: photo)
-
end
-
-
1
def send_to_streams(photo, photo_params)
-
16
aspects = @user.aspects_from_ids(photo_params[:aspect_ids])
-
16
@user.add_to_streams(photo, aspects)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PollParticipationService
-
1
def initialize(user)
-
13
@user = user
-
end
-
-
1
def vote(post_id, answer_id)
-
14
answer = PollAnswer.find(answer_id)
-
11
@user.participate_in_poll!(target(post_id), answer) if target(post_id)
-
end
-
-
1
private
-
-
1
def target(post_id)
-
20
@target ||= @user.find_visible_shareable_by_id(Post, post_id) || raise(ActiveRecord::RecordNotFound.new)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PostService
-
1
def initialize(user=nil)
-
715
@user = user
-
end
-
-
1
def find(id)
-
38
if user
-
35
user.find_visible_shareable_by_id(Post, id)
-
else
-
3
Post.find_by_id_and_public(id, true)
-
end
-
end
-
-
1
def find!(id_or_guid)
-
646
if user
-
605
find_non_public_by_guid_or_id_with_user!(id_or_guid)
-
else
-
41
find_public!(id_or_guid)
-
end
-
end
-
-
1
def present_json
-
PostPresenter.new(post, user)
-
end
-
-
1
def present_interactions_json
-
PostInteractionPresenter.new(post, user)
-
end
-
-
1
def mark_user_notifications(post_id)
-
20
return unless user
-
11
mark_comment_reshare_like_notifications_read(post_id)
-
11
mark_mention_notifications_read(post_id)
-
11
mark_like_on_comment_notifications_read(post_id)
-
end
-
-
1
def destroy(post_id, private_allowed=true)
-
12
post = if private_allowed
-
10
find_non_public_by_guid_or_id_with_user!(post_id)
-
else
-
2
find_public!(post_id)
-
end
-
7
raise Diaspora::NotMine unless post.author == user.person
-
-
4
user.retract(post)
-
end
-
-
1
def mentionable_in_comment(post_id, query)
-
17
post = find!(post_id)
-
15
Person
-
.allowed_to_be_mentioned_in_a_comment_to(post)
-
.where.not(id: user.person_id)
-
.find_by_substring(query)
-
.sort_for_mention_suggestion(post, user)
-
.for_json
-
.limit(15)
-
end
-
-
1
private
-
-
1
attr_reader :user
-
-
1
def find_public!(id_or_guid)
-
43
Post.where(post_key(id_or_guid) => id_or_guid).first.tap do |post|
-
43
raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid}" unless post
-
42
raise Diaspora::NonPublic unless post.public?
-
end
-
end
-
-
1
def find_non_public_by_guid_or_id_with_user!(id_or_guid)
-
615
user.find_visible_shareable_by_id(Post, id_or_guid, key: post_key(id_or_guid)).tap do |post|
-
615
raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid} for user #{user.id}" unless post
-
end
-
end
-
-
# We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
-
1
def post_key(id_or_guid)
-
658
id_or_guid.to_s.length < 16 ? :id : :guid
-
end
-
-
1
def mark_comment_reshare_like_notifications_read(post_id)
-
11
Notification.where(recipient_id: user.id, target_type: "Post", target_id: post_id, unread: true)
-
.update_all(unread: false)
-
end
-
-
1
def mark_mention_notifications_read(post_id)
-
11
mention_ids = Mention.where(
-
mentions_container_id: post_id,
-
mentions_container_type: "Post",
-
person_id: user.person_id
-
).ids
-
11
mention_ids.concat(mentions_in_comments_for_post(post_id).pluck(:id))
-
-
Notification.where(recipient_id: user.id, target_type: "Mention", target_id: mention_ids, unread: true)
-
11
.update_all(unread: false) if mention_ids.any?
-
end
-
-
1
def mentions_in_comments_for_post(post_id)
-
11
Mention
-
.joins("INNER JOIN comments ON mentions_container_id = comments.id AND mentions_container_type = 'Comment'")
-
.where(comments: {commentable_id: post_id, commentable_type: "Post"})
-
end
-
-
1
def mark_like_on_comment_notifications_read(post_id)
-
11
Notification.where(recipient_id: user.id, target_type: "Comment",
-
target_id: Comment.where(commentable_id: post_id, author_id: user.person.id),
-
unread: true).update_all(unread: false) # rubocop:disable Rails/SkipsModelValidations
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ReshareService
-
1
def initialize(user=nil)
-
69
@user = user
-
end
-
-
1
def create(post_id)
-
41
post = post_service.find!(post_id)
-
38
post = post.absolute_root if post.is_a? Reshare
-
38
user.reshare!(post)
-
end
-
-
1
def find_for_post(post_id)
-
28
reshares = post_service.find!(post_id).reshares
-
22
user ? reshares.order(Arel.sql("author_id = #{user.person.id} DESC")) : reshares
-
end
-
-
1
private
-
-
1
attr_reader :user
-
-
1
def post_service
-
69
@post_service ||= PostService.new(user)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class StatusMessageCreationService
-
1
include Rails.application.routes.url_helpers
-
-
1
def initialize(user)
-
97
@user = user
-
end
-
-
1
def create(params)
-
97
validate_content(params)
-
-
95
build_status_message(params).tap do |status_message|
-
95
load_aspects(params[:aspect_ids]) unless status_message.public?
-
90
add_attachments(status_message, params)
-
90
status_message.save
-
90
process(status_message, params[:services])
-
end
-
end
-
-
1
private
-
-
1
attr_reader :user, :aspects
-
-
1
def validate_content(params)
-
97
raise MissingContent unless params[:status_message][:text].present? || params[:photos].present?
-
end
-
-
1
def build_status_message(params)
-
95
public = params[:public] || false
-
95
user.build_post(:status_message, params[:status_message].merge(public: public))
-
end
-
-
1
def add_attachments(status_message, params)
-
90
add_location(status_message, params[:location_address], params[:location_coords])
-
90
add_poll(status_message, params)
-
90
add_photos(status_message, params[:photos])
-
end
-
-
1
def add_location(status_message, address, coordinates)
-
90
status_message.build_location(address: address, coordinates: coordinates) if address.present?
-
end
-
-
1
def add_poll(status_message, params)
-
90
if params[:poll_question].present?
-
11
status_message.build_poll(question: params[:poll_question])
-
11
[*params[:poll_answers]].each do |poll_answer|
-
33
answer = status_message.poll.poll_answers.build(answer: poll_answer)
-
33
answer.poll = status_message.poll
-
end
-
end
-
end
-
-
1
def add_photos(status_message, photos)
-
90
if photos.present?
-
40
status_message.photos << Photo.where(id: photos, author_id: status_message.author_id)
-
40
status_message.photos.each do |photo|
-
54
photo.public = status_message.public
-
54
photo.pending = false
-
end
-
end
-
end
-
-
1
def load_aspects(aspect_ids)
-
42
@aspects = user.aspects_from_ids(aspect_ids)
-
42
raise BadAspectsIDs if aspects.empty?
-
end
-
-
1
def process(status_message, services)
-
90
add_to_streams(status_message) unless status_message.public?
-
88
dispatch(status_message, services)
-
end
-
-
1
def add_to_streams(status_message)
-
37
user.add_to_streams(status_message, aspects)
-
49
status_message.photos.each {|photo| user.add_to_streams(photo, aspects) }
-
end
-
-
1
def dispatch(status_message, services)
-
88
receiving_services = services ? Service.titles(services) : []
-
88
status_message.filter_mentions # this is only required until changes from #6818 are deployed on every pod
-
88
user.dispatch_post(status_message,
-
url: short_post_url(status_message.guid, host: AppConfig.environment.url),
-
service_types: receiving_services)
-
end
-
-
1
class BadAspectsIDs < RuntimeError
-
end
-
-
1
class MissingContent < RuntimeError
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class TagFollowingService
-
1
def initialize(user=nil)
-
32
@user = user
-
end
-
-
1
def create(name)
-
18
name_normalized = ActsAsTaggableOn::Tag.normalize(name)
-
18
raise ArgumentError, "Name field null or empty" if name_normalized.blank?
-
-
11
tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
-
11
raise DuplicateTag if @user.tag_followings.exists?(tag_id: tag.id)
-
-
9
tag_following = @user.tag_followings.new(tag_id: tag.id)
-
9
raise "Can't process tag entity" unless tag_following.save
-
-
9
tag
-
end
-
-
1
def find(name)
-
name_normalized = ActsAsTaggableOn::Tag.normalize(name)
-
ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
-
end
-
-
1
def destroy(id)
-
5
tag_following = @user.tag_followings.find_by!(tag_id: id)
-
4
tag_following.destroy
-
end
-
-
1
def destroy_by_name(name)
-
5
name_normalized = ActsAsTaggableOn::Tag.normalize(name)
-
5
followed_tag = @user.followed_tags.find_by!(name: name_normalized)
-
2
destroy(followed_tag.id)
-
end
-
-
1
def index
-
5
@user.followed_tags
-
end
-
-
1
class DuplicateTag < RuntimeError; end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class ExportedPhotos < SecureUploader
-
def store_dir
-
"uploads/users"
-
end
-
-
def extension_allowlist
-
%w[zip]
-
end
-
-
def filename
-
"diaspora_#{model.username}_photos_#{secure_token}#{extension}"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class ExportedUser < SecureUploader
-
def store_dir
-
"uploads/users"
-
end
-
-
def extension_allowlist
-
%w[gz zip json]
-
end
-
-
def filename
-
"diaspora_#{model.username}_data_#{secure_token}#{extension}"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class ProcessedImage < CarrierWave::Uploader::Base
-
1
include CarrierWave::MiniMagick
-
-
1
def store_dir
-
2660
"uploads/images"
-
end
-
-
1
def extension_allowlist
-
2440
%w[jpg jpeg png gif webp]
-
end
-
-
1
def filename
-
5856
model.random_string + File.extname(@filename) if @filename
-
end
-
-
1
version :thumb_small do
-
1
process resize_to_fill: [50, 50, combine_options: {unsharp: "1.5x1+0.7+0.02"}]
-
end
-
1
version :thumb_medium do
-
1
process resize_to_limit: [100, 100, combine_options: {unsharp: "1.5x1+0.7+0.02"}]
-
end
-
1
version :thumb_large do
-
1
process resize_to_limit: [300, 1500]
-
end
-
1
version :scaled_full do
-
1
process resize_to_limit: [700, nil]
-
end
-
end
-
# frozen_string_literal: true
-
-
class SecureUploader < CarrierWave::Uploader::Base
-
protected
-
-
def extension
-
".#{file.filename.split('.').drop(1).join('.')}" if file.present? && file.respond_to?(:filename)
-
end
-
-
def secure_token(bytes=16)
-
var = :"@#{mounted_as}_secure_token"
-
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.urlsafe_base64(bytes))
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
class UnprocessedImage < CarrierWave::Uploader::Base
-
1
include CarrierWave::MiniMagick
-
-
1
def store_dir
-
6316
"uploads/images"
-
end
-
-
1
def extension_allowlist
-
3451
%w[jpg jpeg png gif webp]
-
end
-
-
1
def filename
-
8257
model.random_string + extension if @filename
-
end
-
-
1
def extension
-
8257
needs_converting? ? ".webp" : File.extname(@filename)
-
end
-
-
1
def needs_converting?
-
8263
extname = File.extname(@filename)
-
8263
%w[.webp .gif].exclude?(extname) && !model.keep_original_format
-
end
-
-
1
process :basic_process
-
-
1
def basic_process
-
7
manipulate! do |img|
-
6
img.combine_options do |i|
-
6
i.auto_orient
-
6
i.strip
-
end
-
-
6
img = yield(img) if block_given?
-
-
6
img.format("webp") if needs_converting?
-
6
img
-
end
-
end
-
-
1
version :thumb_small
-
1
version :thumb_medium
-
1
version :thumb_large
-
1
version :scaled_full do
-
1
process :get_version_dimensions
-
end
-
-
1
def get_version_dimensions
-
6
model.width, model.height = `identify -format "%wx%h " #{file.path}`.split(/x/)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class ArchiveBase < Base
-
1
sidekiq_options queue: :low
-
-
1
include Diaspora::Logging
-
-
1
def perform(*args)
-
7
if currently_running_archive_jobs >= AppConfig.settings.archive_jobs_concurrency.to_i
-
1
logger.info "Already the maximum number of parallel archive jobs running, " \
-
"scheduling #{self.class}:#{args} in 5 minutes."
-
1
self.class.perform_in(5.minutes + rand(30), *args)
-
else
-
6
perform_archive_job(*args)
-
end
-
end
-
-
1
private
-
-
1
def perform_archive_job(_args)
-
raise NotImplementedError, "You must override perform_archive_job"
-
end
-
-
1
def currently_running_archive_jobs
-
7
Sidekiq::Workers.new.count do |process_id, thread_id, work|
-
3
!(Process.pid.to_s == process_id.split(":")[1] && Thread.current.object_id.to_s(36) == thread_id) &&
-
ArchiveBase.subclasses.map(&:to_s).include?(work["payload"]["class"])
-
end
-
rescue Redis::CannotConnectError
-
# If code gets to this point and there is no Redis conenction, we're
-
# running in a Test environment and have not mocked Sidekiq::Workers, so
-
# we're not testing the concurrency-limiting behavior.
-
# There is no way a production pod will run into this code, as diaspora*
-
# refuses to start without redis.
-
3
0
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class Base
-
1
include Sidekiq::Worker
-
1
sidekiq_options backtrace: (bt = AppConfig.environment.sidekiq.backtrace.get) && bt.to_i,
-
1
retry: (rt = AppConfig.environment.sidekiq.retry.get) && rt.to_i
-
-
1
include Diaspora::Logging
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class CheckBirthday < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
6
profiles = Profile
-
.where("EXTRACT(MONTH FROM birthday) = ?", Time.zone.today.month)
-
.where("EXTRACT(DAY FROM birthday) = ?", Time.zone.today.day)
-
6
profiles.each do |profile|
-
40
profile.person.contacts.where(sharing: true, receiving: true).each do |contact|
-
28
Notifications::ContactsBirthday.notify(contact, [])
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Workers
-
class CleanCachedFiles < Base
-
sidekiq_options queue: :low
-
-
def perform
-
CarrierWave.clean_cached_files!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class CleanupOldExports < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
4
User.where("exported_at < ?", 14.days.ago).each do |user|
-
1
user.remove_export = true
-
1
user.exported_at = nil
-
1
user.save
-
end
-
-
4
User.where("exported_photos_at < ?", 14.days.ago).each do |user|
-
1
user.remove_exported_photos_file = true
-
1
user.exported_photos_at = nil
-
1
user.save
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class CleanupPendingPhotos < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
3
Photo.where(pending: true).where("created_at < ?", 1.day.ago).destroy_all
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class DeferredDispatch < Base
-
1
sidekiq_options queue: :high
-
-
1
def perform(user_id, object_class_name, object_id, opts)
-
2316
user = User.find(user_id)
-
2316
object = object_class_name.constantize.find(object_id)
-
-
2315
Diaspora::Federation::Dispatcher.build(user, object, opts.deep_symbolize_keys).dispatch
-
rescue ActiveRecord::RecordNotFound # The target got deleted before the job was run
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class DeferredRetraction < Base
-
1
sidekiq_options queue: :high
-
-
1
def perform(user_id, retraction_class, retraction_data, recipient_ids, opts)
-
6
user = User.find(user_id)
-
6
subscribers = Person.where(id: recipient_ids)
-
6
object = retraction_class.constantize.new(retraction_data.deep_symbolize_keys, subscribers)
-
-
6
Diaspora::Federation::Dispatcher.build(user, object, opts.deep_symbolize_keys).dispatch
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
class DeleteAccount < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(account_deletion_id)
-
2
account_deletion = AccountDeletion.find(account_deletion_id)
-
2
account_deletion.perform!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
1
module Workers
-
1
class DeletePostFromService < Base
-
1
sidekiq_options queue: :high
-
-
1
def perform(service_id, opts)
-
1
service = Service.find_by_id(service_id)
-
1
service.delete_from_service(opts.deep_symbolize_keys)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class ExportPhotos < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(user_id)
-
3
@user = User.find(user_id)
-
3
@user.perform_export_photos!
-
-
3
if @user.reload.exported_photos_file.present?
-
1
ExportMailer.export_photos_complete_for(@user).deliver_now
-
else
-
2
ExportMailer.export_photos_failure_for(@user).deliver_now
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class ExportUser < ArchiveBase
-
1
private
-
-
1
def perform_archive_job(user_id)
-
6
user = User.find(user_id)
-
6
user.perform_export!
-
-
6
if user.reload.export.present?
-
1
ExportMailer.export_complete_for(user).deliver_now
-
else
-
5
ExportMailer.export_failure_for(user).deliver_now
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
class FetchProfilePhoto < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(user_id, service_id, fallback_image_url = nil)
-
4
service = Service.find(service_id)
-
-
4
image_url = service.profile_photo_url
-
4
image_url ||= fallback_image_url
-
-
4
return unless image_url
-
-
3
user = User.find(user_id)
-
-
3
@photo = Photo.diaspora_initialize(:author => user.person, :image_url => image_url, :pending => true)
-
3
@photo.save!
-
-
3
profile_params = {:image_url => @photo.url(:thumb_large),
-
:image_url_medium => @photo.url(:thumb_medium),
-
:image_url_small => @photo.url(:thumb_small)}
-
3
user.update_profile(profile_params)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class FetchPublicPosts < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(diaspora_id)
-
Diaspora::Fetcher::Public.new.fetch!(diaspora_id)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class FetchWebfinger < Base
-
1
sidekiq_options queue: :urgent
-
-
1
def perform(account)
-
2
person = Person.find_or_fetch_by_identifier(account)
-
-
# also, schedule to fetch a few public posts from that person
-
1
Diaspora::Fetcher::Public.queue_for(person)
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
# Ignored
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
module Workers
-
1
class GatherOEmbedData < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(post_id, url, retry_count=1)
-
8
post = Post.find(post_id)
-
7
post.o_embed_cache = OEmbedCache.find_or_create_by(url: url)
-
7
post.save
-
rescue ActiveRecord::RecordNotFound
-
# User created a post and deleted it right afterwards before we
-
# we had a chance to run the job.
-
# On the other hand sometimes the job runs before the Post is
-
# fully persisted. So we just reduce the amount of retries.
-
1
GatherOEmbedData.perform_in(1.minute, post_id, url, retry_count+1) unless retry_count > 3
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
-
1
module Workers
-
1
class GatherOpenGraphData < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(post_id, url, retry_count=1)
-
8
post = Post.find(post_id)
-
7
post.open_graph_cache = OpenGraphCache.find_or_create_by(url: url)
-
7
post.save
-
rescue ActiveRecord::RecordNotFound
-
# User created a post and deleted it right afterwards before we
-
# we had a chance to run the job.
-
# On the other hand sometimes the job runs before the Post is
-
# fully persisted. So we just reduce the amount of retries.
-
1
GatherOpenGraphData.perform_in(1.minute, post_id, url, retry_count+1) unless retry_count > 3
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Workers
-
class ImportUser < ArchiveBase
-
private
-
-
def perform_archive_job(user_id)
-
user = User.find(user_id)
-
ImportService.new.import_by_user(user)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class AlsoCommented < NotifierBase
-
end
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class CommentOnPost < NotifierBase
-
end
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class ConfirmEmail < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class ContactsBirthday < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class CsrfTokenFail < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
module Mail
-
1
class InviteEmail < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(emails, inviter_id, options={})
-
1
EmailInviter.new(emails, User.find(inviter_id), options).send!
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class Liked < NotifierBase
-
1
def perform(*args)
-
6
super
-
rescue ActiveRecord::RecordNotFound => e
-
2
logger.warn("failed to send liked notification mail: #{e.message}")
-
2
raise e unless e.message.start_with?("Couldn't find Like with")
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Workers
-
module Mail
-
class LikedComment < Liked
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
module Mail
-
1
class Mentioned < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class MentionedInComment < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class NotifierBase < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(*args)
-
653
Notifier.send_notification(self.class.name.demodulize.underscore, *args).deliver_now
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
module Mail
-
1
class PrivateMessage < NotifierBase
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class ReportWorker < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(report_id)
-
ReportMailer.new_report(report_id).each(&:deliver_now)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
module Mail
-
1
class Reshared < NotifierBase
-
end
-
end
-
end
-
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
module Mail
-
1
class StartedSharing < NotifierBase
-
end
-
end
-
end
-
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
#
-
1
module Workers
-
1
class PostToService < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(service_id, post_id, url)
-
1
service = Service.find_by_id(service_id)
-
1
post = Post.find_by_id(post_id)
-
1
service.post(post, url)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
1
module Workers
-
1
class ProcessPhoto < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform(id)
-
488
photo = Photo.find(id)
-
487
unprocessed_image = photo.unprocessed_image
-
-
487
return false if photo.processed? || unprocessed_image.path.try(:include?, ".gif")
-
-
485
photo.processed_image.store!(unprocessed_image)
-
485
photo.save!
-
rescue ActiveRecord::RecordNotFound # Deleted before the job was run
-
# Ignored
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class PublishToHub < Base
-
1
sidekiq_options queue: :medium
-
-
1
def perform(sender_atom_url)
-
397
Pubsubhubbub.new(AppConfig.environment.pubsub_server.get).publish(sender_atom_url)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class QueueUsersForRemoval < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
# Queue users for removal due to inactivity
-
8
if AppConfig.settings.maintenance.remove_old_users.enable?
-
6
users = User.where("last_seen < ? and locked_at is null and remove_after is null",
-
6
Time.now - (AppConfig.settings.maintenance.remove_old_users.after_days.to_i).days)
-
.order(:last_seen)
-
.limit(AppConfig.settings.maintenance.remove_old_users.limit_removals_to_per_day)
-
-
# deliver to be closed emails to account holders
-
# and queue accounts for closing to sidekiq
-
# for those who have not signed in, skip warning and queue removal
-
# in +1 days
-
6
users.each do |user|
-
3
if user.sign_in_count > 0
-
2
remove_at = Time.now + AppConfig.settings.maintenance.remove_old_users.warn_days.to_i.days
-
else
-
1
remove_at = Time.now
-
end
-
3
user.flag_for_removal(remove_at)
-
3
if user.sign_in_count > 0
-
# send a warning
-
2
Maintenance.account_removal_warning(user).deliver_now
-
end
-
3
Workers::RemoveOldUser.perform_in(remove_at+1.day, user.id)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class ReceiveBase < Base
-
1
sidekiq_options queue: :urgent
-
-
1
include Diaspora::Logging
-
-
# don't retry for errors that will fail again
-
1
def filter_errors_for_retry
-
139
yield
-
rescue DiasporaFederation::Entity::ValidationError,
-
DiasporaFederation::Parsers::BaseParser::InvalidRootNode,
-
DiasporaFederation::Entity::InvalidEntityName,
-
DiasporaFederation::Entity::UnknownEntity,
-
DiasporaFederation::Entities::Signable::PublicKeyNotFound,
-
DiasporaFederation::Entities::Signable::SignatureVerificationFailed,
-
DiasporaFederation::Entities::Participation::ParentNotLocal,
-
DiasporaFederation::Federation::Receiver::InvalidSender,
-
DiasporaFederation::Federation::Receiver::NotPublic,
-
DiasporaFederation::Salmon::SenderKeyNotFound,
-
DiasporaFederation::Salmon::InvalidEnvelope,
-
DiasporaFederation::Salmon::InvalidSignature,
-
DiasporaFederation::Salmon::InvalidDataType,
-
DiasporaFederation::Salmon::InvalidAlgorithm,
-
DiasporaFederation::Salmon::InvalidEncoding,
-
Diaspora::Federation::AuthorIgnored,
-
Diaspora::Federation::InvalidAuthor,
-
Diaspora::Federation::RecipientClosed => e
-
60
logger.warn "don't retry for error: #{e.class}"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class ReceiveLocal < Base
-
1
sidekiq_options queue: :high
-
-
1
def perform(object_class_string, object_id, recipient_user_ids)
-
2044
object = object_class_string.constantize.find(object_id)
-
-
2044
object.receive(recipient_user_ids) if object.respond_to?(:receive)
-
-
2044
NotificationService.new.notify(object, recipient_user_ids)
-
rescue ActiveRecord::RecordNotFound # Already deleted before the job could run
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class ReceivePrivate < ReceiveBase
-
1
def perform(user_id, data)
-
52
filter_errors_for_retry do
-
52
user_private_key = User.where(id: user_id).pluck(:serialized_private_key).first
-
52
rsa_key = OpenSSL::PKey::RSA.new(user_private_key)
-
52
DiasporaFederation::Federation::Receiver.receive_private(data, rsa_key, user_id)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class ReceivePublic < ReceiveBase
-
1
def perform(data)
-
87
filter_errors_for_retry do
-
87
DiasporaFederation::Federation::Receiver.receive_public(data)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class RecheckScheduledPods < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
1
Pod.check_scheduled!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class RecurringPodCheck < Base
-
1
sidekiq_options queue: :low
-
-
1
def perform
-
1
Pod.check_all!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Workers
-
1
class RemoveOldUser < Base
-
1
sidekiq_options queue: :low
-
-
1
def safe_remove_after
-
# extra safety time to compare in addition to remove_after
-
2
Time.now-
-
(AppConfig.settings.maintenance.remove_old_users.after_days.to_i).days-
-
2
(AppConfig.settings.maintenance.remove_old_users.warn_days.to_i).days
-
end
-
-
1
def perform(user_id)
-
# if user has been flagged as to be removed (see settings.maintenance.remove_old_users)
-
# and hasn't logged in since that flag has been set, we remove the user
-
5
if AppConfig.settings.maintenance.remove_old_users.enable?
-
3
user = User.find(user_id)
-
3
if user.remove_after < Time.now and user.last_seen < self.safe_remove_after
-
1
user.close_account!
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class ResetPassword < Base
-
1
sidekiq_options queue: :urgent
-
-
1
def perform(user_id)
-
2
User.find(user_id).send_reset_password_instructions!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class SendBase < Base
-
1
sidekiq_options queue: :medium, retry: 0
-
-
1
MAX_RETRIES = AppConfig.environment.sidekiq.retry.get.to_i
-
-
1
protected
-
-
1
def schedule_retry(retry_count, sender_id, obj_str, failed_urls)
-
6
if retry_count < (obj_str.start_with?("Contact") ? MAX_RETRIES + 10 : MAX_RETRIES)
-
3
yield(seconds_to_delay(retry_count), retry_count)
-
else
-
3
logger.warn "status=abandon sender=#{sender_id} obj=#{obj_str} failed_urls='[#{failed_urls.join(', ')}]'"
-
3
raise MaxRetriesReached
-
end
-
end
-
-
1
private
-
-
# based on Sidekiq::Middleware::Server::RetryJobs#seconds_to_delay
-
1
def seconds_to_delay(count)
-
24
((count + 3)**4) + (rand(30) * (count + 1))
-
end
-
-
# send job to the dead job queue
-
1
class MaxRetriesReached < RuntimeError
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class SendPrivate < SendBase
-
1
def perform(sender_id, obj_str, targets, retry_count=0)
-
13
targets_to_retry = DiasporaFederation::Federation::Sender.private(sender_id, obj_str, targets)
-
-
13
return if targets_to_retry.empty?
-
-
4
schedule_retry(retry_count + 1, sender_id, obj_str, targets_to_retry.keys) do |delay, new_retry_count|
-
2
Workers::SendPrivate.perform_in(delay, sender_id, obj_str, targets_to_retry, new_retry_count)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Workers
-
1
class SendPublic < SendBase
-
1
def perform(sender_id, obj_str, urls, xml, retry_count=0)
-
5
urls_to_retry = DiasporaFederation::Federation::Sender.public(sender_id, obj_str, urls, xml)
-
-
5
return if urls_to_retry.empty?
-
-
2
schedule_retry(retry_count + 1, sender_id, obj_str, urls_to_retry) do |delay, new_retry_count|
-
1
Workers::SendPublic.perform_in(delay, sender_id, obj_str, urls_to_retry, xml, new_retry_count)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class AccountDeleter
-
# Things that are not removed from the database:
-
# - Comments
-
# - Likes
-
# - Messages
-
# - NotificationActors
-
#
-
# Given that the User in question will be tombstoned, all of the
-
# above will come from an anonomized account (via the UI).
-
# The deleted user will appear as "Deleted Account" in
-
# the interface.
-
-
attr_accessor :person, :user
-
-
def initialize(person)
-
self.person = person
-
self.user = person.owner
-
end
-
-
def perform!
-
# close person
-
delete_standard_person_associations
-
delete_contacts_of_me
-
tombstone_person_and_profile
-
-
close_user if user
-
-
mark_account_deletion_complete
-
end
-
-
# user deletion methods
-
def close_user
-
remove_share_visibilities_on_contacts_posts
-
disconnect_contacts
-
delete_standard_user_associations
-
delete_user_invitation_code
-
tombstone_user
-
end
-
-
# user deletions
-
def normal_ar_user_associates_to_delete
-
%i[tag_followings services aspects user_preferences
-
notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers]
-
end
-
-
def delete_standard_user_associations
-
normal_ar_user_associates_to_delete.each do |asso|
-
user.send(asso).ids.each_slice(20) do |ids|
-
User.reflect_on_association(asso).class_name.constantize.where(id: ids).destroy_all
-
end
-
end
-
end
-
-
def delete_user_invitation_code
-
InvitationCode.find_by(user_id: user.id).try(:destroy)
-
end
-
-
def normal_ar_person_associates_to_delete
-
%i[posts photos mentions participations roles blocks conversation_visibilities]
-
end
-
-
def delete_standard_person_associations
-
normal_ar_person_associates_to_delete.each do |asso|
-
person.send(asso).ids.each_slice(20) do |ids|
-
Person.reflect_on_association(asso).class_name.constantize.where(id: ids).destroy_all
-
end
-
end
-
end
-
-
def disconnect_contacts
-
user.contacts.destroy_all
-
end
-
-
# Currently this would get deleted due to the db foreign key constrainsts,
-
# but we'll keep this method here for completeness
-
def remove_share_visibilities_on_contacts_posts
-
ShareVisibility.for_a_user(user).find_each(batch_size: 20, &:destroy)
-
end
-
-
def tombstone_person_and_profile
-
person.lock_access!
-
person.clear_profile!
-
end
-
-
def tombstone_user
-
user.clear_account!
-
end
-
-
def delete_contacts_of_me
-
Contact.all_contacts_of_person(person).find_each(batch_size: 20, &:destroy)
-
end
-
-
def mark_account_deletion_complete
-
AccountDeletion.find_by(person: person)&.update(completed_at: Time.now.utc)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
module AuthorizationPoint
-
1
class Endpoint
-
1
attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type,
-
:scopes, :request_uri, :request_object, :nonce
-
1
delegate :call, to: :app
-
-
1
def initialize(user)
-
45
@user = user
-
45
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
-
44
build_from_request_object(req)
-
44
build_attributes(req, res)
-
38
if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ")
-
38
handle_response_type(req, res)
-
else
-
req.unsupported_response_type!
-
end
-
end
-
end
-
-
1
def build_attributes(req, res)
-
44
build_client(req)
-
44
build_redirect_uri(req, res)
-
42
verify_nonce(req, res)
-
41
build_scopes(req)
-
end
-
-
1
def handle_response_type(_req, _res)
-
raise NotImplementedError # Implemented by subclass
-
end
-
-
1
private
-
-
1
def build_client(req)
-
44
@o_auth_application = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request!
-
end
-
-
1
def build_redirect_uri(req, res)
-
44
res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris)
-
end
-
-
1
def verify_nonce(req, res)
-
42
req.invalid_request! "nonce required" if res.protocol_params_location == :fragment && req.nonce.blank?
-
end
-
-
1
def build_scopes(req)
-
41
@scopes = req.scope.map {|scope|
-
63
scope.tap do |scope_name|
-
req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.unknown_scope", scope_name: scope_name) \
-
63
unless auth_scopes.include?(scope_name)
-
end
-
}
-
-
40
@scopes.push("public:read") unless @scopes.include?("public:read")
-
40
has_private_scope = @scopes.include?("private:read") || @scopes.include?("private:modify")
-
40
has_contacts_scope = @scopes.include? "contacts:read"
-
2
req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.private_contacts_linkage_error") \
-
40
if has_private_scope && !has_contacts_scope
-
end
-
-
1
def auth_scopes
-
63
Api::OpenidConnect::Authorization::SCOPES
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
module AuthorizationPoint
-
1
class EndpointConfirmationPoint < Endpoint
-
1
def initialize(current_user, approved=false)
-
15
super(current_user)
-
15
@approved = approved
-
end
-
-
1
def handle_response_type(req, res)
-
15
handle_approval(@approved, req, res)
-
end
-
-
1
def handle_approval(approved, req, res)
-
15
if approved
-
11
approved!(req, res)
-
else
-
4
req.access_denied!
-
end
-
end
-
-
1
def build_from_request_object(_req)
-
# Empty
-
end
-
-
1
private
-
-
1
def approved!(req, res)
-
11
auth = find_or_build_auth(req)
-
11
handle_approved_response_type(auth, req, res)
-
11
res.approve!
-
end
-
-
1
def find_or_build_auth(req)
-
11
OpenidConnect::Authorization.find_or_create_by!(
-
o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri).tap do |auth|
-
11
auth.nonce = req.nonce
-
11
auth.scopes = @scopes
-
11
auth.save
-
end
-
end
-
-
1
def handle_approved_response_type(auth, req, res)
-
11
response_types = Array(req.response_type)
-
11
handle_approved_auth_code(auth, res, response_types)
-
11
handle_approved_access_token(auth, res, response_types)
-
11
handle_approved_id_token(auth, res, response_types)
-
end
-
-
1
def handle_approved_auth_code(auth, res, response_types)
-
11
return unless response_types.include?(:code)
-
2
res.code = auth.create_code
-
end
-
-
1
def handle_approved_access_token(auth, res, response_types)
-
11
return unless response_types.include?(:token)
-
3
res.access_token = auth.create_access_token
-
end
-
-
1
def handle_approved_id_token(auth, res, response_types)
-
11
return unless response_types.include?(:id_token)
-
9
id_token = auth.create_id_token
-
9
res.id_token = id_token.to_jwt(code: res.try(:code), access_token: res.try(:access_token))
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
module AuthorizationPoint
-
1
class EndpointStartPoint < Endpoint
-
1
def build_from_request_object(req)
-
29
request_object = build_request_object(req)
-
29
return unless request_object
-
2
claims = request_object.raw_attributes.with_indifferent_access[:claims].try(:[], :userinfo).try(:keys)
-
2
return unless claims
-
1
req.update_param("scope", req.scope + claims)
-
end
-
-
1
def handle_response_type(req, _res)
-
23
@response_type = req.response_type
-
end
-
-
1
private
-
-
1
def build_request_object(req)
-
29
if req.request_uri.present?
-
OpenIDConnect::RequestObject.fetch req.request_uri
-
29
elsif req.request.present?
-
2
OpenIDConnect::RequestObject.decode req.request
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
module Error
-
1
class InvalidRedirectUri < ::ArgumentError
-
1
def initialize
-
super "Redirect uri contains fragment"
-
end
-
end
-
1
class InvalidSectorIdentifierUri < ::ArgumentError
-
1
def initialize
-
super "Invalid sector identifier uri"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/app/models/id_token.rb
-
-
1
require "uri"
-
-
1
module Api
-
1
module OpenidConnect
-
1
class IdToken
-
1
def initialize(authorization, nonce)
-
19
@authorization = authorization
-
19
@nonce = nonce
-
19
@created_at = Time.current
-
19
@expires_at = 30.minutes.from_now
-
end
-
-
1
def to_jwt(options={})
-
19
to_response_object(options).to_jwt(OpenidConnect::IdTokenConfig::PRIVATE_KEY) do |jwt|
-
19
jwt.kid = :default
-
end
-
end
-
-
1
private
-
-
1
def to_response_object(options={})
-
19
OpenIDConnect::ResponseObject::IdToken.new(claims).tap do |id_token|
-
19
id_token.code = options[:code] if options[:code]
-
19
id_token.access_token = options[:access_token] if options[:access_token]
-
end
-
end
-
-
1
def claims
-
19
sub = build_sub
-
19
@claims ||= {
-
iss: AppConfig.environment.url,
-
sub: sub,
-
aud: @authorization.o_auth_application.client_id,
-
exp: @expires_at.to_i,
-
iat: @created_at.to_i,
-
auth_time: @authorization.user.current_sign_in_at.to_i,
-
nonce: @nonce,
-
acr: 0
-
}
-
end
-
-
1
def build_sub
-
19
Api::OpenidConnect::SubjectIdentifierCreator.create(@authorization)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
class IdTokenConfig
-
1
key_file_path = File.join(Rails.root, "config", "oidc_key.pem")
-
1
if File.exist?(key_file_path)
-
1
private_key = OpenSSL::PKey::RSA.new(File.read(key_file_path))
-
else
-
private_key = OpenSSL::PKey::RSA.new(4096)
-
File.write key_file_path, private_key.to_pem
-
File.chmod(0600, key_file_path)
-
end
-
1
PRIVATE_KEY = private_key
-
1
PUBLIC_KEY = private_key.public_key
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2011 nov matake
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
# See https://github.com/nov/openid_connect_sample/blob/master/lib/authentication.rb#L56
-
-
1
module Api
-
1
module OpenidConnect
-
1
module ProtectedResourceEndpoint
-
1
attr_reader :current_token
-
-
1
def require_access_token(required_scopes)
-
47
raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless
-
462
access_token?(required_scopes)
-
end
-
-
1
def access_token?(required_scopes)
-
543
@current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
-
raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless
-
543
@current_token && @current_token.authorization
-
540
@current_token.authorization.try(:accessible?, required_scopes)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module OpenidConnect
-
1
module SubjectIdentifierCreator
-
1
def self.create(auth)
-
23
if auth.o_auth_application.ppid?
-
10
identifier = auth.o_auth_application.sector_identifier_uri ||
-
URI.parse(auth.o_auth_application.redirect_uris[0]).host
-
pairwise_pseudonymous_identifier =
-
10
auth.user.pairwise_pseudonymous_identifiers.find_or_create_by(identifier: identifier)
-
10
pairwise_pseudonymous_identifier.guid
-
else
-
13
auth.user.diaspora_handle
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Inspired by https://github.com/nov/openid_connect_sample/blob/master/lib/token_endpoint.rb
-
-
1
module Api
-
1
module OpenidConnect
-
1
class TokenEndpoint
-
1
attr_accessor :app
-
1
delegate :call, to: :app
-
-
1
def initialize
-
23
@app = Rack::OAuth2::Server::Token.new do |req, res|
-
19
o_auth_app = retrieve_client(req)
-
19
if app_valid?(o_auth_app, req)
-
15
handle_flows(req, res)
-
else
-
4
req.invalid_client!
-
end
-
end
-
end
-
-
1
def handle_flows(req, res)
-
15
case req.grant_type
-
when :refresh_token
-
3
handle_refresh_flow(req, res)
-
when :authorization_code
-
12
auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code)
-
12
req.invalid_grant! if auth.blank?
-
9
res.access_token = auth.create_access_token
-
9
res.access_token.refresh_token = auth.refresh_token
-
9
if auth.accessible? "openid"
-
9
id_token = auth.create_id_token
-
9
res.id_token = id_token.to_jwt(access_token: res.access_token)
-
end
-
else
-
req.unsupported_grant_type!
-
end
-
end
-
-
1
def handle_refresh_flow(req, res)
-
# Handle as if scope request was omitted even if provided.
-
# See https://tools.ietf.org/html/rfc6749#section-6 for handling
-
3
auth = Api::OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token
-
3
if auth
-
2
res.access_token = auth.create_access_token
-
else
-
1
req.invalid_grant!
-
end
-
end
-
-
1
def retrieve_client(req)
-
19
Api::OpenidConnect::OAuthApplication.find_by client_id: req.client_id
-
end
-
-
1
def app_valid?(o_auth_app, req)
-
19
o_auth_app.try(:client_secret) == req.client_secret
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module Paging
-
1
class IndexPaginator
-
1
def initialize(query_base, current_page, limit)
-
37
@query_base = query_base
-
37
@current_page = current_page.to_i
-
37
@limit = limit.to_i
-
end
-
-
1
def page_data
-
102
@page_data ||= @query_base.paginate(page: @current_page, per_page: @limit)
-
102
@max_page = (@query_base.count * 1.0 / @limit * 1.0).ceil
-
102
@max_page = 1 if @max_page < 1
-
102
@page_data
-
end
-
-
1
def next_page(for_url=true)
-
37
page_data
-
37
return nil if for_url && @current_page == @max_page
-
-
7
return "page=#{@current_page + 1}" if for_url
-
-
3
IndexPaginator.new(@query_base, @current_page + 1, @limit)
-
end
-
-
1
def previous_page(for_url=true)
-
29
page_data
-
29
return nil if for_url && @current_page == 1
-
-
return "page=#{@current_page - 1}" if for_url
-
-
IndexPaginator.new(@query_base, @current_page - 1, @limit)
-
end
-
-
1
def filter_parameters(parameters)
-
parameters.delete(:page)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module Paging
-
1
class RestPagedResponseBuilder
-
1
def initialize(pager, request, allowed_params=nil)
-
78
@pager = pager
-
78
@base_url = request.original_url.split("?").first if request
-
78
@query_parameters = if allowed_params
-
allowed_params
-
78
elsif request&.query_parameters
-
75
request&.query_parameters
-
else
-
3
{}
-
end
-
end
-
-
1
def response
-
{
-
76
links: navigation_builder,
-
data: @pager.page_data
-
}
-
end
-
-
1
private
-
-
1
def navigation_builder
-
76
previous_page = @pager.previous_page
-
76
links = {}
-
76
links[:previous] = link_builder(previous_page) if previous_page
-
-
76
next_page = @pager.next_page
-
76
links[:next] = link_builder(next_page) if next_page
-
-
76
links
-
end
-
-
1
def link_builder(page_parameter)
-
84
"#{@base_url}?#{filtered_original_parameters}#{page_parameter}"
-
end
-
-
1
def filtered_original_parameters
-
84
@pager.filter_parameters(@query_parameters)
-
84
return "" if @query_parameters.empty?
-
-
192
@query_parameters.map {|k, v| "#{k}=#{v}" }.join("&") + "&"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module Paging
-
1
class RestPaginatorBuilder
-
1
MAX_LIMIT = 100
-
1
DEFAULT_LIMIT = 15
-
-
1
def initialize(base_query, request, allow_default_page=true, default_limit=DEFAULT_LIMIT)
-
77
@base_query = base_query
-
77
@request = request
-
77
@allow_default_page = allow_default_page
-
77
@default_limit = if default_limit < MAX_LIMIT && default_limit > 0
-
77
default_limit
-
else
-
DEFAULT_LIMIT
-
end
-
end
-
-
1
def index_pager(params)
-
29
current_page = current_page_settings(params)
-
29
paged_response_builder(IndexPaginator.new(@base_query, current_page, limit_settings(params)))
-
end
-
-
1
def time_pager(params, query_time_field="created_at", data_time_field=query_time_field)
-
48
is_descending, current_time = time_settings(params)
-
48
paged_response_builder(
-
TimePaginator.new(
-
query_base: @base_query,
-
query_time_field: query_time_field,
-
data_time_field: data_time_field,
-
current_time: current_time,
-
is_descending: is_descending,
-
limit: limit_settings(params)
-
)
-
)
-
end
-
-
1
private
-
-
1
def current_page_settings(params)
-
29
if params["page"]
-
1
requested_page = params["page"].to_i
-
1
requested_page = 1 if requested_page < 1
-
1
requested_page
-
28
elsif @allow_default_page
-
28
1
-
else
-
raise ActionController::ParameterMissing
-
end
-
end
-
-
1
def paged_response_builder(paginator)
-
77
Api::Paging::RestPagedResponseBuilder.new(paginator, @request)
-
end
-
-
1
def time_settings(params)
-
48
time_params = params.permit("before", "after")
-
48
time_params["before"] = (Time.current + 1.year).iso8601 if time_params.empty? && @allow_default_page
-
-
48
raise "Missing time parameters for query building" if time_params.empty?
-
-
48
if time_params["before"]
-
45
is_descending = true
-
45
current_time = Time.iso8601(time_params["before"])
-
else
-
3
is_descending = false
-
3
current_time = Time.iso8601(time_params["after"])
-
end
-
48
[is_descending, current_time]
-
end
-
-
1
def limit_settings(params)
-
77
requested_limit = params["per_page"].to_i if params["per_page"]
-
77
return @default_limit unless requested_limit
-
-
1
requested_limit = [1, requested_limit].max
-
1
[requested_limit, MAX_LIMIT].min
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Api
-
1
module Paging
-
1
class TimePaginator
-
1
def initialize(opts={})
-
52
@query_base = opts[:query_base]
-
52
@query_time_field = opts[:query_time_field]
-
52
@data_time_field = opts[:data_time_field]
-
52
@current_time = opts[:current_time]
-
52
@limit = opts[:limit]
-
52
@is_descending = opts[:is_descending]
-
52
direction = if @is_descending
-
47
"<"
-
else
-
5
">"
-
end
-
52
@time_query_string = "#{@query_time_field} #{direction} ?"
-
52
@sort_string = if @is_descending
-
47
"#{@query_time_field} DESC"
-
else
-
5
"#{@query_time_field} ASC"
-
end
-
end
-
-
1
def page_data
-
145
return @data if @data
-
-
51
@data = @query_base.where([@time_query_string, @current_time.iso8601(3)]).limit(@limit).order(@sort_string)
-
153
time_data = @data.map {|d| d[@data_time_field] }.sort
-
51
@min_time = time_data.first
-
51
@max_time = time_data.last + 0.001.seconds if time_data.last
-
-
51
@data
-
end
-
-
1
def next_page(for_url=true)
-
47
page_data
-
47
return nil unless next_time
-
-
42
return next_page_as_query_parameter if for_url
-
-
TimePaginator.new(
-
query_base: @query_base,
-
query_time_field: @query_time_field,
-
query_data_field: @data_time_field,
-
current_time: next_time,
-
is_descending: @is_descending,
-
limit: @limit
-
)
-
end
-
-
1
def previous_page(for_url=true)
-
47
page_data
-
47
return nil unless previous_time
-
-
42
return previous_page_as_query_parameter if for_url
-
-
TimePaginator.new(
-
query_base: @query_base,
-
query_time_field: @query_time_field,
-
query_data_field: @data_time_field,
-
current_time: previous_time,
-
is_descending: !@is_descending,
-
limit: @limit
-
)
-
end
-
-
1
def filter_parameters(parameters)
-
84
parameters.delete(:before)
-
84
parameters.delete(:after)
-
end
-
-
1
private
-
-
1
def next_time
-
89
if @is_descending
-
83
@min_time
-
else
-
6
@max_time
-
end
-
end
-
-
1
def previous_time
-
89
if @is_descending
-
83
@max_time
-
else
-
6
@min_time
-
end
-
end
-
-
1
def next_page_as_query_parameter
-
42
if @is_descending
-
39
"before=#{next_time.iso8601(3)}"
-
else
-
3
"after=#{next_time.iso8601(3)}"
-
end
-
end
-
-
1
def previous_page_as_query_parameter
-
42
if @is_descending
-
39
"after=#{previous_time.iso8601(3)}"
-
else
-
3
"before=#{previous_time.iso8601(3)}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
include ArchiveHelper
-
1
include Diaspora::Logging
-
-
1
attr_accessor :user
-
-
1
def initialize(archive_hash)
-
14
@archive_hash = archive_hash
-
end
-
-
1
def import(opts={})
-
10
import_tag_followings
-
10
import_aspects
-
10
import_contacts
-
10
import_posts
-
10
import_relayables
-
10
import_subscriptions
-
10
import_others_relayables
-
10
import_blocks
-
10
import_settings if opts.fetch(:import_settings, true)
-
10
import_profile if opts.fetch(:import_profile, true)
-
end
-
-
1
def find_or_create_user(attr)
-
6
allowed_keys = %w[email language]
-
6
data = convert_keys(archive_hash["user"], allowed_keys)
-
# setting getting_started to false as the user doesn't need to see the getting started wizard
-
6
data.merge!(
-
username: attr[:username],
-
password: attr[:password],
-
password_confirmation: attr[:password],
-
person: {
-
profile_attributes: profile_attributes
-
}
-
)
-
6
self.user = User.find_or_build(data)
-
6
user.getting_started = false
-
6
user.save!
-
end
-
-
1
private
-
-
1
attr_reader :archive_hash
-
-
1
def profile_attributes
-
15
allowed_keys = %w[first_name last_name image_url bio gender location birthday searchable nsfw tag_string]
-
15
profile_data = archive_hash["user"]["profile"]["entity_data"]
-
15
convert_keys(profile_data, allowed_keys).tap do |attrs|
-
15
attrs[:public_details] = profile_data["public"]
-
end
-
end
-
-
1
def import_contacts
-
10
import_collection(contacts, ContactImporter)
-
end
-
-
1
def set_auto_follow_back_aspect
-
1
name = archive_hash["user"]["auto_follow_back_aspect"]
-
1
return if name.nil?
-
-
1
aspect = user.aspects.find_by(name: name)
-
1
user.update(auto_follow_back: true, auto_follow_back_aspect: aspect) if aspect
-
end
-
-
1
def import_aspects
-
10
contact_groups.each do |group|
-
begin
-
4
user.aspects.create!(group.slice("name"))
-
rescue ActiveRecord::RecordInvalid => e
-
logger.warn "#{self}: #{e}"
-
end
-
end
-
end
-
-
1
def import_posts
-
10
import_collection(posts, PostImporter)
-
end
-
-
1
def import_relayables
-
10
import_collection(relayables, OwnRelayableImporter)
-
end
-
-
1
def import_others_relayables
-
10
import_collection(others_relayables, EntityImporter)
-
end
-
-
1
def import_blocks
-
10
import_collection(blocks, BlockImporter)
-
end
-
-
1
def import_collection(collection, importer_class)
-
50
collection.each do |object|
-
30
importer_class.new(object, user).import
-
end
-
end
-
-
1
def import_tag_followings
-
10
archive_hash.fetch("user").fetch("followed_tags", []).each do |tag_name|
-
begin
-
2
tag = ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name)
-
2
user.tag_followings.create!(tag: tag)
-
rescue ActiveRecord::RecordInvalid => e
-
1
logger.warn "#{self}: #{e}"
-
end
-
end
-
end
-
-
1
def import_subscriptions
-
10
post_subscriptions.each do |post_guid|
-
7
post = Post.find_or_fetch_by(archive_author_diaspora_id, post_guid)
-
7
if post.nil?
-
2
logger.warn "#{self}: post with guid #{post_guid} not found, can't subscribe"
-
2
next
-
end
-
begin
-
5
user.participations.create!(target: post)
-
rescue ActiveRecord::RecordInvalid => e
-
1
logger.warn "#{self}: #{e}"
-
end
-
end
-
end
-
-
1
def import_settings
-
9
allowed_keys = %w[language show_community_spotlight_in_stream]
-
9
convert_keys(archive_hash["user"], allowed_keys).each do |key, value|
-
2
user.update(key => value) unless value.nil?
-
end
-
-
9
set_auto_follow_back_aspect if archive_hash.fetch("user").fetch("auto_follow_back", false)
-
end
-
-
1
def import_profile
-
9
profile_attributes.each do |key, value|
-
19
user.person.profile.update(key => value) unless value.nil?
-
end
-
end
-
-
1
def convert_keys(hash, allowed_keys)
-
30
hash
-
.slice(*allowed_keys)
-
.symbolize_keys
-
end
-
-
1
def to_s
-
4
"#{self.class}:#{archive_author_diaspora_id}:#{user.diaspora_handle}"
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
module ArchiveHelper
-
1
def posts
-
55
@posts ||= archive_hash.fetch("user").fetch("posts", [])
-
end
-
-
1
def relayables
-
17
@relayables ||= archive_hash.fetch("user").fetch("relayables", [])
-
end
-
-
1
def others_relayables
-
17
@others_relayables ||= archive_hash.fetch("others_data", {}).fetch("relayables", [])
-
end
-
-
1
def post_subscriptions
-
10
archive_hash.fetch("user").fetch("post_subscriptions", [])
-
end
-
-
1
def contacts
-
22
archive_hash.fetch("user").fetch("contacts", [])
-
end
-
-
1
def contact_groups
-
10
@contact_groups ||= archive_hash.fetch("user").fetch("contact_groups", [])
-
end
-
-
1
def archive_author_diaspora_id
-
59
@archive_author_diaspora_id ||= archive_hash.fetch("user").fetch("profile").fetch("entity_data").fetch("author")
-
end
-
-
1
def person
-
10
@person ||= Person.find_or_fetch_by_identifier(archive_author_diaspora_id)
-
end
-
-
1
def blocks
-
10
@blocks ||= archive_hash.fetch("user").fetch("blocks", [])
-
end
-
-
1
def private_key
-
6
OpenSSL::PKey::RSA.new(serialized_private_key)
-
end
-
-
1
def serialized_private_key
-
11
archive_hash.fetch("user").fetch("private_key")
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class BlockImporter
-
1
include Diaspora::Logging
-
1
attr_reader :json, :user
-
-
1
def initialize(json, user)
-
2
@json = json
-
2
@user = user
-
end
-
-
1
def import
-
2
p = Person.find_or_fetch_by_identifier(json)
-
1
migrant_person = handle_migrant_person(p)
-
1
user.blocks.create(person_id: migrant_person.id)
-
rescue ActiveRecord::RecordInvalid,
-
DiasporaFederation::Discovery::DiscoveryError => e
-
1
logger.warn "#{self}: #{e}"
-
end
-
-
1
private
-
-
1
def handle_migrant_person(person)
-
1
return person if person.account_migration.nil?
-
-
person.account_migration.newest_person
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class ContactImporter
-
1
include Diaspora::Logging
-
-
1
def initialize(json, user)
-
8
@json = json
-
8
@user = user
-
end
-
-
1
attr_reader :json
-
1
attr_reader :user
-
-
1
def import
-
8
return unless json.fetch("receiving")
-
-
6
@imported_contact = create_contact
-
5
add_to_aspects
-
rescue ActiveRecord::RecordInvalid => e
-
1
logger.warn "#{self}: #{e}"
-
end
-
-
1
private
-
-
1
def add_to_aspects
-
5
json.fetch("contact_groups_membership", []).each do |group_name|
-
5
aspect = user.aspects.find_by(name: group_name)
-
5
if aspect.nil?
-
4
logger.warn "#{self}: aspect \"#{group_name}\" is missing"
-
4
next
-
end
-
1
@imported_contact.aspects << aspect
-
end
-
end
-
-
1
def create_contact
-
6
person = Person.by_account_identifier(json.fetch("account_id"))
-
# see AccountMigration#dispatch_contacts for the other half of this when the contact is sharing with the user
-
6
user.contacts.create!(person_id: person.id, sharing: false, receiving: true)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class EntityImporter
-
1
include ArchiveValidator::EntitiesHelper
-
1
include Diaspora::Logging
-
-
1
def initialize(json, user)
-
44
@json = json
-
44
@user = user
-
end
-
-
1
def import
-
44
self.persisted_object = Diaspora::Federation::Receive.perform(entity, skip_relaying: true)
-
rescue DiasporaFederation::Entities::Signable::SignatureVerificationFailed,
-
DiasporaFederation::Discovery::InvalidDocument,
-
DiasporaFederation::Discovery::DiscoveryError,
-
DiasporaFederation::Federation::Fetcher::NotFetchable,
-
OwnRelayableImporter::NoParentError,
-
ActiveRecord::RecordInvalid => e
-
6
logger.warn "#{self}: #{e}"
-
6
self.persisted_object = nil
-
end
-
-
1
attr_reader :json
-
1
attr_reader :user
-
1
attr_accessor :persisted_object
-
-
1
def entity
-
32
entity_class.from_json(json)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class OwnEntityImporter < EntityImporter
-
1
def import
-
35
substitute_author
-
35
super
-
rescue Diaspora::Federation::InvalidAuthor
-
11
return if real_author == old_author_id
-
-
7
logger.warn "#{self.class}: attempt to import an entity with guid \"#{guid}\" which belongs to #{real_author}"
-
end
-
-
1
private
-
-
1
def substitute_author
-
35
@old_author_id = entity_data["author"]
-
35
entity_data["author"] = user.diaspora_handle
-
end
-
-
1
attr_reader :old_author_id
-
-
1
def persisted_object
-
20
return @persisted_object if defined?(@persisted_object)
-
-
5
@persisted_object = (instance if real_author == old_author_id)
-
end
-
-
1
def real_author
-
23
instance.author.diaspora_handle
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class OwnRelayableImporter < OwnEntityImporter
-
1
class NoParentError < RuntimeError; end
-
-
1
def entity
-
12
fetch_parent(symbolized_entity_data)
-
11
entity_class.new(symbolized_entity_data)
-
end
-
-
1
private
-
-
1
def symbolized_entity_data
-
23
@symbolized_entity_data ||= entity_data.slice(*entity_class.class_props.keys.map(&:to_s)).symbolize_keys
-
end
-
-
# Copied over from DiasporaFederation::Entities::Relayable
-
1
def fetch_parent(data)
-
12
type = data.fetch(:parent_type) {
-
10
break entity_class::PARENT_TYPE if entity_class.const_defined?(:PARENT_TYPE)
-
}
-
12
entity = Diaspora::Federation::Mappings.model_class_for(type).find_by(guid: data.fetch(:parent_guid))
-
12
raise NoParentError if entity.nil?
-
-
11
data[:parent] = Diaspora::Federation::Entities.related_entity(entity)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveImporter
-
1
class PostImporter < OwnEntityImporter
-
1
include Diaspora::Logging
-
-
1
def import
-
20
super
-
20
import_subscriptions if persisted_object
-
end
-
-
1
private
-
-
1
def substitute_author
-
20
super
-
20
return unless entity_type == "status_message"
-
-
17
entity_data["photos"].each do |photo|
-
5
photo["entity_data"]["author"] = user.diaspora_handle
-
end
-
end
-
-
1
def import_subscriptions
-
16
json.fetch("subscribed_users_ids", []).each do |diaspora_id|
-
begin
-
5
person = Person.find_or_fetch_by_identifier(diaspora_id)
-
4
person = person.account_migration.newest_person unless person.account_migration.nil?
-
4
next if person.closed_account?
-
# TODO: unless person.nil? import subscription: subscription import is not supported yet
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "yajl"
-
-
# ArchiveValidator checks for errors in archive. It also find non-critical problems and fixes them in the archive hash
-
# so that the ArchiveImporter doesn't have to handle this issues. Non-critical problems found are indicated as warnings.
-
# Also it performs necessary data fetch where required.
-
1
class ArchiveValidator
-
1
include ArchiveImporter::ArchiveHelper
-
-
1
def initialize(archive)
-
11
@archive = archive
-
end
-
-
1
def validate
-
6
run_validators(CRITICAL_VALIDATORS, errors)
-
4
run_validators(NON_CRITICAL_VALIDATORS, warnings)
-
rescue KeyError => e
-
1
errors.push("Missing mandatory data: #{e}")
-
rescue Yajl::ParseError => e
-
1
errors.push("Bad JSON provided: #{e}")
-
end
-
-
1
def errors
-
14
@errors ||= []
-
end
-
-
1
def warnings
-
7
@warnings ||= []
-
end
-
-
1
def archive_hash
-
40
@archive_hash ||= Yajl::Parser.new.parse(archive)
-
end
-
-
CRITICAL_VALIDATORS = [
-
1
SchemaValidator,
-
AuthorPrivateKeyValidator
-
].freeze
-
-
NON_CRITICAL_VALIDATORS = [
-
1
ContactsValidator,
-
PostsValidator,
-
RelayablesValidator,
-
OthersRelayablesValidator
-
].freeze
-
-
1
private_constant :CRITICAL_VALIDATORS, :NON_CRITICAL_VALIDATORS
-
-
1
private
-
-
1
attr_reader :archive
-
-
1
def run_validators(list, messages)
-
10
list.each do |validator_class|
-
27
validator = validator_class.new(archive_hash)
-
25
messages.concat(validator.messages)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class AuthorPrivateKeyValidator < BaseValidator
-
1
include Diaspora::Logging
-
-
1
def validate
-
9
return if person.public_key.export == private_key.public_key.export
-
-
1
messages.push("Private key in the archive doesn't match the known key of #{person.diaspora_handle}")
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
2
logger.info "Archive author couldn't be fetched (old home pod is down?), will continue with data"\
-
" import only"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class BaseValidator
-
1
include ArchiveImporter::ArchiveHelper
-
1
attr_reader :archive_hash
-
-
1
def initialize(archive_hash)
-
132
@archive_hash = archive_hash
-
132
validate
-
end
-
-
1
def messages
-
258
@messages ||= []
-
end
-
-
1
def valid?
-
98
@valid.nil? ? messages.empty? : @valid
-
end
-
-
1
private
-
-
1
attr_writer :valid
-
-
1
def validate; end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class CollectionValidator < BaseValidator
-
# Runs validations over each element in collection and removes every element
-
# which fails the validations. Any messages produced by the entity_validator are
-
# concatenated to the messages of the CollectionValidator instance.
-
1
def validate
-
26
collection.keep_if do |item|
-
66
subvalidator = entity_validator.new(archive_hash, item)
-
66
messages.concat(subvalidator.messages)
-
66
subvalidator.valid?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class ContactValidator < BaseValidator
-
1
def initialize(archive_hash, contact)
-
18
@contact = contact
-
18
super(archive_hash)
-
end
-
-
1
private
-
-
1
def validate
-
18
handle_migrant_contact
-
17
self.valid = account_open?
-
rescue DiasporaFederation::Discovery::DiscoveryError => e
-
1
messages.push("#{self.class}: failed to fetch person #{diaspora_id}: #{e}")
-
1
self.valid = false
-
end
-
-
1
attr_reader :contact
-
-
1
def diaspora_id
-
26
contact.fetch("account_id")
-
end
-
-
1
def handle_migrant_contact
-
18
return if person.account_migration.nil?
-
-
4
contact["account_id"] = person.account_migration.newest_person.diaspora_handle
-
4
@person = nil
-
end
-
-
1
def person
-
39
@person ||= Person.find_or_fetch_by_identifier(diaspora_id)
-
end
-
-
1
def account_open?
-
17
!person.closed_account? || (messages.push("#{self.class}: account #{diaspora_id} is closed") && false)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class ContactsValidator < CollectionValidator
-
1
def collection
-
7
contacts
-
end
-
-
1
def entity_validator
-
12
ContactValidator
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
module EntitiesHelper
-
1
private
-
-
1
def instance
-
25
@instance ||= model_class.find_by(guid: guid)
-
end
-
-
1
def entity_type
-
193
json.fetch("entity_type")
-
end
-
-
1
def entity_data
-
179
json.fetch("entity_data")
-
end
-
-
1
def model_class
-
11
@model_class ||= Diaspora::Federation::Mappings.model_class_for(entity_type.camelize)
-
end
-
-
1
def entity_class
-
95
DiasporaFederation::Entity.entity_class(entity_type)
-
end
-
-
1
def guid
-
38
@guid ||= entity_data.fetch("guid")
-
end
-
-
1
def to_s
-
20
"#{entity_class.class_name}:#{guid}"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class OthersRelayablesValidator < CollectionValidator
-
1
def collection
-
7
others_relayables
-
end
-
-
1
def entity_validator
-
6
RelayableValidator
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class OwnRelayableValidator < RelayableValidator
-
1
private
-
-
1
def post_find_by_guid(guid)
-
15
super || by_guid(Post, guid)
-
end
-
-
1
def post_find_by_poll_guid(guid)
-
10
super || by_guid(Poll, guid)&.status_message
-
end
-
-
1
def by_guid(klass, guid)
-
23
klass.find_or_fetch_by(archive_author_diaspora_id, guid)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class PostValidator < BaseValidator
-
1
include EntitiesHelper
-
-
1
def initialize(archive_hash, post)
-
29
@json = post
-
29
super(archive_hash)
-
end
-
-
1
private
-
-
1
def validate
-
29
return unless entity_type == "reshare" && entity_data["root_guid"].nil?
-
-
6
messages.push("reshare #{self} doesn't have a root, ignored")
-
end
-
-
1
attr_reader :json
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class PostsValidator < CollectionValidator
-
1
def collection
-
7
posts
-
end
-
-
1
def entity_validator
-
27
PostValidator
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
# We have to validate relayables before import because during import we'll not be able to fetch parent anymore
-
# because parent author will point to ourselves.
-
1
class RelayableValidator < BaseValidator
-
1
include EntitiesHelper
-
-
1
def initialize(archive_hash, relayable)
-
38
@relayable = relayable
-
38
super(archive_hash)
-
end
-
-
1
private
-
-
1
def validate
-
38
self.valid = parent_present?
-
end
-
-
1
attr_reader :relayable
-
1
alias json relayable
-
-
# Common methods used by subclasses:
-
-
1
def missing_parent_message
-
8
messages.push("Parent entity for #{self} is missing. Impossible to import, ignoring.")
-
end
-
-
1
def parent_present?
-
38
parent.present? || (missing_parent_message && false)
-
end
-
-
1
def parent
-
38
@parent ||= find_parent
-
end
-
-
1
def find_parent
-
38
if entity_type == "poll_participation"
-
13
post_find_by_poll_guid(parent_guid)
-
else
-
25
post_find_by_guid(parent_guid)
-
end
-
end
-
-
1
def parent_guid
-
38
entity_data.fetch("parent_guid")
-
end
-
-
1
def post_find_by_guid(guid)
-
25
posts.find {|post|
-
55
post.fetch("entity_data").fetch("guid") == guid
-
}
-
end
-
-
1
def post_find_by_poll_guid(guid)
-
13
posts.find {|post|
-
45
post.fetch("entity_data").fetch("poll", nil)&.fetch("entity_data", nil)&.fetch("guid", nil) == guid
-
}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class RelayablesValidator < CollectionValidator
-
1
def collection
-
7
relayables
-
end
-
-
1
def entity_validator
-
15
OwnRelayableValidator
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ArchiveValidator
-
1
class SchemaValidator < BaseValidator
-
1
JSON_SCHEMA = "lib/schemas/archive-format.json"
-
-
1
def validate
-
6
return if JSON::Validator.validate(JSON_SCHEMA, archive_hash)
-
-
2
messages.push("Archive schema validation failed")
-
end
-
end
-
end
-
-
# frozen_string_literal: true
-
-
1
class BookmarkletRenderer
-
1
class << self
-
1
def cached_name
-
1
@cached_name ||= if Rails.application.config.assets.compile
-
1
"bookmarklet.js"
-
else
-
Rails.application.assets_manifest.assets["bookmarklet.js"]
-
end
-
end
-
-
1
def cached_path
-
48
@cached_path ||= Rails.root.join("public", "assets", cached_name)
-
end
-
-
1
def source
-
16
@source ||= Rails.application.assets["bookmarklet.js"].filename
-
end
-
-
1
def body
-
16
unless File.exist?(cached_path) || Rails.application.config.assets.compile
-
raise "Please run the rake task to compile the bookmarklet: `bin/rake assets:precompile`"
-
end
-
-
16
compile if Rails.application.config.assets.compile
-
16
@body ||= File.read(cached_path)
-
end
-
-
1
def compile
-
16
src = File.read(source)
-
16
@body = Terser.compile(src)
-
16
FileUtils.mkdir_p cached_path.dirname
-
32
File.open(cached_path, "w") {|f| f.write(@body) }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Configuration
-
KNOWN_SERVICES = %i[twitter tumblr wordpress].freeze
-
-
module Methods
-
def pod_uri
-
return @pod_uri.dup unless @pod_uri.nil?
-
-
url = environment.url.get
-
-
begin
-
@pod_uri = Addressable::URI.heuristic_parse(url)
-
rescue
-
puts "WARNING: pod url #{url} is not a legal URI"
-
end
-
-
@pod_uri.scheme = "https" if environment.require_ssl?
-
@pod_uri.path = "/"
-
-
@pod_uri.dup
-
end
-
-
# @param path [String]
-
# @return [String]
-
def url_to(path)
-
pod_uri.tap {|uri| uri.path = path }.to_s
-
end
-
-
def bare_pod_uri
-
pod_uri.authority.gsub('www.', '')
-
end
-
-
def configured_services
-
return @configured_services unless @configured_services.nil?
-
-
@configured_services = []
-
KNOWN_SERVICES.each do |service|
-
@configured_services << service if services.send(service).enable?
-
end
-
-
@configured_services
-
end
-
attr_writer :configured_services
-
-
def show_service?(service, user)
-
return false unless self["services.#{service}.enable"]
-
# Return true only if 'authorized' is true or equal to user username
-
(user && self["services.#{service}.authorized"] == user.username) ||
-
self["services.#{service}.authorized"] == true
-
end
-
-
def local_posts_stream?(user)
-
return true if settings.enable_local_posts_stream == "admins" &&
-
user.admin?
-
return true if settings.enable_local_posts_stream == "moderators" &&
-
user.moderator?
-
-
settings.enable_local_posts_stream == "everyone"
-
end
-
-
def secret_token
-
if heroku?
-
return ENV["SECRET_TOKEN"] if ENV["SECRET_TOKEN"]
-
-
warn "FATAL: Running on Heroku with SECRET_TOKEN unset"
-
warn " Run heroku config:add SECRET_TOKEN=#{SecureRandom.hex(40)}"
-
abort
-
else
-
token_file = File.expand_path(
-
"../config/initializers/secret_token.rb",
-
File.dirname(__FILE__)
-
)
-
system "DISABLE_SPRING=1 bin/rake generate:secret_token" unless File.exist? token_file
-
require token_file
-
Diaspora::Application.config.secret_key_base
-
end
-
end
-
-
def version_string
-
return @version_string unless @version_string.nil?
-
-
@version_string = version.number.to_s
-
@version_string = "#{@version_string}-p#{git_revision[0..7]}" if git_available?
-
@version_string
-
end
-
-
def git_available?
-
return @git_available unless @git_available.nil?
-
-
if heroku?
-
@git_available = false
-
else
-
`which git`
-
`git status 2> /dev/null` if $?.success?
-
@git_available = $?.success?
-
end
-
end
-
-
def git_revision
-
get_git_info if git_available?
-
@git_revision
-
end
-
-
def git_update
-
get_git_info if git_available?
-
@git_update
-
end
-
-
def rails_asset_id
-
(git_revision || version)[0..8]
-
end
-
-
def get_redis_options
-
redis_url = ENV["REDIS_URL"] || environment.redis.get
-
-
return {} unless redis_url.present?
-
-
unless redis_url.start_with?("redis://", "unix:///")
-
warn "WARNING: Your redis url (#{redis_url}) doesn't start with redis:// or unix:///"
-
end
-
{url: redis_url}
-
end
-
-
def sidekiq_log
-
path = Pathname.new environment.sidekiq.log.get
-
path = Rails.root.join(path) unless path.absolute?
-
path.to_s
-
end
-
-
def postgres?
-
ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
-
end
-
-
def mysql?
-
ActiveRecord::Base.connection.adapter_name == "Mysql2"
-
end
-
-
def bitcoin_donation_address
-
if AppConfig.settings.bitcoin_address.present?
-
AppConfig.settings.bitcoin_address
-
end
-
end
-
-
private
-
-
def get_git_info
-
return if git_info_present? || !git_available?
-
-
git_cmd = `git log -1 --pretty="format:%H %ci"`
-
if git_cmd =~ /^(\w+?)\s(.+)$/
-
@git_revision = $1
-
@git_update = $2.strip
-
end
-
end
-
-
def git_info_present?
-
@git_revision || @git_update
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class ConnectionTester
-
1
include Diaspora::Logging
-
-
1
NODEINFO_FRAGMENT = "/.well-known/nodeinfo"
-
-
1
class << self
-
# Test the reachability of a server by the given HTTP/S URL.
-
# In the first step, a DNS query is performed to check whether the
-
# given name even resolves correctly.
-
# The second step is to send a HTTP request and look at the returned
-
# status code or any returned errors.
-
# This function isn't intended to check for the availability of a
-
# specific page, instead a GET request is sent to the root directory
-
# of the server.
-
# In the third step an attempt is made to determine the software version
-
# used on the server, via the nodeinfo page.
-
#
-
# @api This is the entry point you're supposed to use for testing
-
# connections to other diaspora-compatible servers.
-
# @param [String] url URL
-
# @return [Result] result object containing information about the
-
# server and to what point the connection was successful
-
1
def check(url)
-
2
result = Result.new
-
-
begin
-
2
ct = ConnectionTester.new(url, result)
-
-
# test DNS resolving
-
1
ct.resolve
-
-
# test HTTP request
-
ct.request
-
-
# test for the diaspora* version
-
ct.nodeinfo
-
-
rescue Failure => e
-
2
result_from_failure(result, e)
-
end
-
-
2
result.freeze
-
end
-
-
1
private
-
-
# infer some attributes of the result object based on the failure
-
1
def result_from_failure(result, error)
-
2
result.error = error
-
-
2
case error
-
when AddressFailure, DNSFailure, NetFailure
-
2
result.reachable = false
-
when SSLFailure
-
result.reachable = true
-
result.ssl = false
-
when HTTPFailure
-
result.reachable = true
-
when NodeInfoFailure
-
result.software_version = ""
-
end
-
end
-
end
-
-
# @raise [AddressFailure] if the specified url is not http(s)
-
1
def initialize(url, result=Result.new)
-
25
@url ||= url
-
25
@result ||= result
-
25
@uri ||= URI.parse(@url)
-
2
raise AddressFailure,
-
25
"invalid protocol: '#{@uri.scheme.upcase}'" unless http_uri?(@uri)
-
rescue AddressFailure => e
-
2
raise e
-
rescue URI::InvalidURIError => e
-
raise AddressFailure, e.message
-
rescue StandardError => e
-
unexpected_error(e)
-
end
-
-
# Perform the DNS query, the IP address will be stored in the result
-
# @raise [DNSFailure] caused by a failure to resolve or a timeout
-
1
def resolve
-
3
@result.ip = IPSocket.getaddress(@uri.host)
-
rescue SocketError => e
-
2
raise DNSFailure, "'#{@uri.host}' - #{e.message}"
-
rescue StandardError => e
-
unexpected_error(e)
-
end
-
-
# Perform a HTTP GET request to determine the following information
-
# * is the host reachable
-
# * is port 80/443 open
-
# * is the SSL certificate valid (only on HTTPS)
-
# * does the server return a successful HTTP status code
-
# * is there a reasonable amount of redirects (3 by default)
-
# (can't do a HEAD request, since that's not a defined route in the app)
-
#
-
# @raise [NetFailure, SSLFailure, HTTPFailure] if any of the checks fail
-
# @return [Integer] HTTP status code
-
1
def request
-
9
with_http_connection do |http|
-
18
capture_response_time { handle_http_response(http.get("/")) }
-
end
-
rescue HTTPFailure => e
-
1
raise e
-
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
-
1
raise NetFailure, e.message
-
rescue Faraday::SSLError => e
-
1
raise SSLFailure, e.message
-
rescue ArgumentError, Faraday::ClientError, Faraday::ServerError => e
-
3
raise HTTPFailure, "#{e.class}: #{e.message}"
-
rescue StandardError => e
-
unexpected_error(e)
-
end
-
-
# Try to find out the version of the other servers software.
-
# Assuming the server speaks nodeinfo
-
#
-
# @raise [HTTPFailure] if the document can't be fetched
-
# @raise [NodeInfoFailure] if the document can't be parsed or is invalid
-
1
def nodeinfo
-
10
with_http_connection do |http|
-
10
ni_resp = http.get(NODEINFO_FRAGMENT)
-
9
ni_urls = find_nodeinfo_urls(ni_resp.body)
-
7
raise NodeInfoFailure, "No supported NodeInfo version found" if ni_urls.empty?
-
-
6
version, url = ni_urls.max
-
6
find_software_version(version, http.get(url).body)
-
end
-
rescue Faraday::ClientError => e
-
1
raise HTTPFailure, "#{e.class}: #{e.message}"
-
rescue NodeInfoFailure => e
-
2
raise e
-
rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError, Faraday::TimeoutError => e
-
2
raise NodeInfoFailure, "#{e.class}: #{e.message}"
-
rescue JSON::JSONError => e
-
1
raise NodeInfoFailure, e.message[0..255].encode(Encoding.default_external, undef: :replace)
-
rescue StandardError => e
-
unexpected_error(e)
-
end
-
-
1
private
-
-
1
def with_http_connection
-
19
@http ||= Faraday.new(@url) do |c|
-
19
c.use Faraday::Response::RaiseError
-
19
c.use Faraday::FollowRedirects::Middleware, limit: 3
-
19
c.adapter(Faraday.default_adapter)
-
19
c.headers[:user_agent] = "diaspora-connection-tester"
-
19
c.options.timeout = 12
-
19
c.options.open_timeout = 6
-
# use the configured CA
-
19
c.ssl.ca_file = Faraday.default_connection.ssl.ca_file
-
end
-
19
yield(@http) if block_given?
-
end
-
-
1
def http_uri?(uri)
-
25
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
-
end
-
-
# request root path, measure response time
-
# measured time may be skewed, if there are redirects
-
#
-
# @return [Faraday::Response]
-
1
def capture_response_time
-
9
start = Time.zone.now
-
9
resp = yield if block_given?
-
3
@result.rt = ((Time.zone.now - start) * 1000.0).to_i # milliseconds
-
3
resp
-
end
-
-
1
def handle_http_response(response)
-
4
@result.status_code = Integer(response.status)
-
-
4
raise HTTPFailure, "unsuccessful response code: #{response.status}" unless response.success?
-
4
raise HTTPFailure, "redirected to other hostname: #{response.env.url}" unless @uri.host == response.env.url.host
-
-
3
@result.reachable = true
-
3
@result.ssl = (response.env.url.scheme == "https")
-
end
-
-
# walk the JSON document, get the actual document locations
-
1
def find_nodeinfo_urls(body)
-
9
jrd = JSON.parse(body)
-
8
links = jrd.fetch("links")
-
8
raise NodeInfoFailure, "invalid JRD: '#/links' is not an array!" unless links.is_a?(Array)
-
-
28
supported_rel_map = NodeInfo::VERSIONS.index_by {|v| "http://nodeinfo.diaspora.software/ns/schema/#{v}" }
-
7
links.map {|entry|
-
10
version = supported_rel_map[entry.fetch("rel")]
-
10
[version, entry.fetch("href")] if version
-
}.compact.to_h
-
end
-
-
# walk the JSON document, find the version string
-
1
def find_software_version(version, body)
-
5
info = JSON.parse(body)
-
5
JSON::Validator.validate!(NodeInfo.schema(version), info)
-
4
sw = info.fetch("software")
-
4
@result.software_version = "#{sw.fetch('name')} #{sw.fetch('version')}"
-
end
-
-
1
def unexpected_error(error)
-
logger.error "unexpected error: #{error.class}: #{error.message}\n#{error.backtrace.first(15).join("\n")}"
-
raise Failure, error.inspect
-
end
-
-
1
class Failure < StandardError
-
end
-
-
1
class AddressFailure < Failure
-
end
-
-
1
class DNSFailure < Failure
-
end
-
-
1
class NetFailure < Failure
-
end
-
-
1
class SSLFailure < Failure
-
end
-
-
1
class HTTPFailure < Failure
-
end
-
-
1
class NodeInfoFailure < Failure
-
end
-
-
1
Result = Struct.new(
-
:ip, :reachable, :ssl, :status_code, :rt, :software_version, :error
-
) do
-
# @!attribute ip
-
# @return [String] resolved IP address from DNS query
-
-
# @!attribute reachable
-
# @return [Boolean] whether the host was reachable over the network
-
-
# @!attribute ssl
-
# @return [Boolean] whether the host has working ssl
-
-
# @!attribute status_code
-
# @return [Integer] HTTP status code that was returned for the HEAD request
-
-
# @!attribute rt
-
# @return [Integer] response time for the HTTP request
-
-
# @!attribute software_version
-
# @return [String] version of diaspora* as reported by nodeinfo
-
-
# @!attribute error
-
# @return [Exception] if the test is unsuccessful, this will contain
-
# an exception of type {ConnectionTester::Failure}
-
-
1
def initialize
-
30
self.rt = -1
-
end
-
-
1
def success?
-
error.nil?
-
end
-
-
1
def error?
-
20
!error.nil?
-
end
-
-
1
def failure_message
-
8
"#{error.class.name}: #{error.message}" if error?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module Diaspora
-
require "diaspora/camo"
-
require "diaspora/exceptions"
-
require "diaspora/exporter"
-
require "diaspora/federated"
-
require "diaspora/federation"
-
require "diaspora/fetcher"
-
require "diaspora/markdownify"
-
require "diaspora/mentionable"
-
require "diaspora/message_renderer"
-
end
-
# frozen_string_literal: true
-
-
# implicitly requires OpenSSL
-
module Diaspora
-
module Camo
-
def self.from_markdown(markdown_text)
-
return unless markdown_text
-
markdown_text = markdown_text.gsub(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/m) do |link|
-
link.gsub($4, self.image_url($4))
-
end
-
markdown_text.gsub(/src=(['"])(.+?)\1/m) do |link|
-
link.gsub($2, self.image_url($2))
-
end
-
end
-
-
def self.image_url(url)
-
return unless url
-
return url unless self.url_eligible?(url)
-
-
begin
-
url = Addressable::URI.encode(Addressable::URI.unencode(url))
-
rescue Addressable::URI::InvalidURIError
-
return url
-
end
-
-
digest = OpenSSL::HMAC.hexdigest(
-
OpenSSL::Digest.new('sha1'),
-
AppConfig.privacy.camo.key,
-
url
-
)
-
-
encoded_url = url.to_enum(:each_byte).map {|byte| '%02x' % byte}.join
-
File.join(AppConfig.privacy.camo.root, digest, encoded_url)
-
end
-
-
def self.url_eligible?(url)
-
return false unless url.start_with?('http', '//')
-
return false if url.start_with?(AppConfig.environment.url.to_s,
-
AppConfig.privacy.camo.root.to_s)
-
true
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Diaspora
-
1
module Commentable
-
1
def self.included(model)
-
1
model.instance_eval do
-
1
has_many :comments, :as => :commentable, :dependent => :destroy
-
end
-
end
-
-
# @return [Array<Comment>]
-
1
def last_three_comments
-
32
return [] if self.comments_count == 0
-
# DO NOT USE .last(3) HERE. IT WILL FETCH ALL COMMENTS AND RETURN THE LAST THREE
-
# INSTEAD OF DOING THE FOLLOWING, AS EXPECTED (THX AR):
-
2
self.comments.order('created_at DESC').limit(3).includes(:author => :profile).reverse
-
end
-
-
# @return [Integer]
-
1
def update_comments_counter
-
697
self.class.where(:id => self.id).
-
update_all(:comments_count => self.comments.count)
-
end
-
-
1
def comments_authors
-
Person.where(id: comments.select(:author_id).distinct)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
class EntityFinder
-
1
def initialize(type, guid)
-
33
@type = type
-
33
@guid = guid
-
end
-
-
1
def class_name
-
34
@class_name ||= DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
-
end
-
-
1
def find
-
34
Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
-
end
-
-
1
private
-
-
1
attr_reader :type, :guid
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module Diaspora
-
# the post in question is not public, and that is somehow a problem
-
class NonPublic < StandardError
-
end
-
-
# the account was closed and that should not be the case if we want
-
# to continue
-
class AccountClosed < StandardError
-
end
-
-
# something that should be accessed does not belong to the current user and
-
# that prevents further execution
-
class NotMine < StandardError
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module Diaspora
-
class Exporter
-
SERIALIZED_VERSION = "2.0"
-
-
def initialize(user)
-
@user = user
-
end
-
-
def execute
-
JSON.generate full_archive
-
end
-
-
private
-
-
def full_archive
-
{version: SERIALIZED_VERSION}
-
.merge(Export::UserSerializer.new(@user.id).as_json)
-
.merge(Export::OthersDataSerializer.new(@user.id).as_json)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
class Exporter
-
# This class implements methods that allow to query relayables (comments, likes, participations,
-
# poll_participations) of other people for posts of the given person.
-
1
class OthersRelayables
-
# @param person_id [Integer] Database id of a person for whom we want to request relayalbes
-
1
def initialize(person_id)
-
31
@person_id = person_id
-
end
-
-
# Comments of other people to the person's post
-
# @return [Comment::ActiveRecord_Relation]
-
1
def comments
-
29
Comment
-
.where.not(author_id: person_id)
-
.joins("INNER JOIN posts ON (commentable_type = 'Post' AND posts.id = commentable_id)")
-
.where("posts.author_id = ?", person_id)
-
end
-
-
# Likes of other people to the person's post
-
# @return [Like::ActiveRecord_Relation]
-
1
def likes
-
29
Like
-
.where.not(author_id: person_id)
-
.joins("INNER JOIN posts ON (target_type = 'Post' AND posts.id = target_id)")
-
.where("posts.author_id = ?", person_id)
-
end
-
-
# Poll participations of other people to the person's polls
-
# @return [PollParticipation::ActiveRecord_Relation]
-
1
def poll_participations
-
29
PollParticipation
-
.where.not(author_id: person_id).joins(:status_message)
-
.where("posts.author_id = ?", person_id)
-
end
-
-
1
private
-
-
1
attr_reader :person_id
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
module Diaspora
-
module Federated
-
require "diaspora/federated/base"
-
require "diaspora/federated/retraction"
-
require "diaspora/federated/contact_retraction"
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
# including this module lets you federate an object at the most basic of level
-
-
module Diaspora
-
module Federated
-
module Base
-
# object for local recipients
-
def object_to_receive
-
self
-
end
-
-
# @abstract
-
# @note this must return [Array<Person>]
-
# @return [Array<Person>]
-
def subscribers
-
raise "You must override subscribers in order to enable federation on this model"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
class ContactRetraction < Retraction
-
def self.entity_class
-
DiasporaFederation::Entities::Contact
-
end
-
-
def self.retraction_data_for(target)
-
Diaspora::Federation::Entities.build(target).to_h
-
end
-
-
def self.for(target)
-
target.receiving = false if target.is_a?(Contact)
-
super
-
end
-
-
def public?
-
false
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module Federated
-
1
module Fetchable
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def find_or_fetch_by(diaspora_id, guid)
-
30
instance = find_by(guid: guid)
-
30
return instance if instance.present?
-
-
16
DiasporaFederation::Federation::Fetcher.fetch_public(diaspora_id, to_s, guid)
-
10
find_by(guid: guid)
-
rescue DiasporaFederation::Federation::Fetcher::NotFetchable
-
6
nil
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module Federated
-
1
class Generator
-
1
include Diaspora::Logging
-
-
1
def initialize(user, target)
-
1107
@user = user
-
1107
@target = target
-
end
-
-
1
def create!(options={})
-
1089
relayable = build(options)
-
1089
if relayable.save!
-
1077
logger.info "user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}"
-
1077
Diaspora::Federation::Dispatcher.defer_dispatch(@user, relayable)
-
1077
relayable
-
end
-
end
-
-
1
def build(options={})
-
1103
self.class.federated_class.new(options.merge(relayable_options).merge(author_id: @user.person.id))
-
end
-
-
1
protected
-
-
1
def relayable_options
-
raise NotImplementedError, "You must override relayable_options"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Retraction
-
include Diaspora::Federated::Base
-
include Diaspora::Logging
-
-
attr_reader :subscribers, :data
-
-
def initialize(data, subscribers, target=nil)
-
@data = data
-
@subscribers = subscribers
-
@target = target
-
end
-
-
def self.entity_class
-
DiasporaFederation::Entities::Retraction
-
end
-
-
def self.retraction_data_for(target)
-
DiasporaFederation::Entities::Retraction.new(
-
target_guid: target.guid,
-
target: Diaspora::Federation::Entities.related_entity(target),
-
target_type: Diaspora::Federation::Mappings.entity_name_for(target),
-
author: target.diaspora_handle
-
).to_h
-
end
-
-
def self.for(target)
-
federation_retraction_data = retraction_data_for(target)
-
new(federation_retraction_data, target.subscribers.select(&:remote?), target)
-
end
-
-
def defer_dispatch(user, include_target_author=true)
-
subscribers = dispatch_subscribers(include_target_author)
-
Workers::DeferredRetraction.perform_async(user.id, self.class.to_s, data.deep_stringify_keys,
-
subscribers.map(&:id), service_opts(user).deep_stringify_keys)
-
end
-
-
def perform
-
logger.debug "Performing retraction for #{target.class.base_class}:#{target.guid}"
-
target.destroy!
-
logger.info "event=retraction status=complete target=#{data[:target_type]}:#{data[:target_guid]}"
-
end
-
-
def public?
-
data[:target][:public]
-
end
-
-
private
-
-
attr_reader :target
-
-
def dispatch_subscribers(include_target_author)
-
subscribers << target.author if target.is_a?(Diaspora::Relayable) && include_target_author && target.author.remote?
-
subscribers
-
end
-
-
def service_opts(user)
-
return {} unless target.is_a?(StatusMessage)
-
-
user.services.each_with_object(service_types: []) do |service, opts|
-
service_opts = service.post_opts(target)
-
if service_opts
-
opts.merge!(service_opts)
-
opts[:service_types] << service.class.to_s
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
# Raised, if author is ignored by the relayable parent author
-
class AuthorIgnored < RuntimeError
-
end
-
-
# Raised, if the author of the existing object doesn't match the received author
-
class InvalidAuthor < RuntimeError
-
end
-
-
# Raised, if the recipient account is closed already
-
class RecipientClosed < RuntimeError
-
end
-
end
-
end
-
-
require "diaspora/federation/dispatcher"
-
require "diaspora/federation/entities"
-
require "diaspora/federation/mappings"
-
require "diaspora/federation/receive"
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
class Dispatcher
-
include Diaspora::Logging
-
-
def initialize(sender, object, opts={})
-
@sender = sender
-
@object = object
-
@opts = opts
-
end
-
-
def self.build(sender, object, opts={})
-
sender = object.try(:sender_for_dispatch) || sender
-
if object.try(:public?)
-
Public.new(sender, object, opts)
-
else
-
Private.new(sender, object, opts)
-
end
-
end
-
-
def self.defer_dispatch(sender, object, opts={})
-
Workers::DeferredDispatch.perform_async(sender.id, object.class.to_s, object.id, opts.deep_stringify_keys)
-
end
-
-
def dispatch
-
deliver_to_services
-
deliver_to_subscribers
-
end
-
-
private
-
-
attr_reader :sender, :object, :opts
-
-
def entity
-
@entity ||= Entities.build(object)
-
end
-
-
def magic_envelope
-
@magic_envelope ||= DiasporaFederation::Salmon::MagicEnvelope.new(
-
entity, sender.diaspora_handle
-
).envelop(sender.encryption_key)
-
end
-
-
def deliver_to_services
-
deliver_to_user_services if opts[:service_types]
-
end
-
-
def deliver_to_subscribers
-
local_people, remote_people = subscribers.uniq(&:id).partition(&:local?)
-
-
deliver_to_local(local_people) unless local_people.empty?
-
deliver_to_remote(remote_people)
-
end
-
-
def deliver_to_local(people)
-
object_to_receive = object.object_to_receive
-
return unless object_to_receive
-
Workers::ReceiveLocal.perform_async(object_to_receive.class.to_s, object_to_receive.id, people.map(&:owner_id))
-
end
-
-
def deliver_to_remote(_people)
-
raise NotImplementedError, "This is an abstract base method. Implement in your subclass."
-
end
-
-
def deliver_to_user_services
-
case object
-
when StatusMessage
-
each_service {|service| Workers::PostToService.perform_async(service.id, object.id, opts[:url]) }
-
when Retraction
-
each_service {|service| Workers::DeletePostFromService.perform_async(service.id, opts.deep_stringify_keys) }
-
end
-
end
-
-
def each_service
-
sender.services.where(type: opts[:service_types]).each {|service| yield(service) }
-
end
-
-
def subscribers
-
opts[:subscribers] || subscribers_from_ids || object.subscribers
-
end
-
-
def subscribers_from_ids
-
Person.where(id: opts[:subscriber_ids]) if opts[:subscriber_ids]
-
end
-
end
-
end
-
end
-
-
require "diaspora/federation/dispatcher/private"
-
require "diaspora/federation/dispatcher/public"
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
class Dispatcher
-
class Private < Dispatcher
-
private
-
-
def deliver_to_remote(people)
-
return if people.empty?
-
-
Workers::SendPrivate.perform_async(sender.id, entity.to_s, targets(people))
-
end
-
-
def targets(people)
-
active, inactive = people.partition {|person| person.pod.active? }
-
logger.info "ignoring inactive pods: #{inactive.map(&:diaspora_handle).join(', ')}" if inactive.any?
-
active.map {|person| [person.receive_url, encrypted_magic_envelope(person)] }.to_h
-
end
-
-
def encrypted_magic_envelope(person)
-
DiasporaFederation::Salmon::EncryptedMagicEnvelope.encrypt(magic_envelope, person.public_key)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
class Dispatcher
-
class Public < Dispatcher
-
private
-
-
def deliver_to_services
-
deliver_to_hub if object.instance_of?(StatusMessage)
-
super
-
end
-
-
def deliver_to_remote(people)
-
targets = target_urls(people)
-
-
return if targets.empty?
-
-
Workers::SendPublic.perform_async(sender.id, entity.to_s, targets, magic_envelope.to_xml)
-
end
-
-
def target_urls(people)
-
active, inactive = Pod.where(id: people.map(&:pod_id).uniq).partition(&:active?)
-
logger.info "ignoring inactive pods: #{inactive.join(', ')}" if inactive.any?
-
active.map {|pod| pod.url_to("/receive/public") }
-
end
-
-
def deliver_to_hub
-
logger.debug "deliver to pubsubhubbub sender: #{sender.diaspora_handle}"
-
Workers::PublishToHub.perform_async(sender.atom_url)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
module Entities
-
def self.build(entity)
-
public_send(Mappings.builder_for(entity), entity)
-
end
-
-
def self.post(post)
-
case post
-
when StatusMessage
-
status_message(post)
-
when Reshare
-
reshare(post)
-
else
-
raise ArgumentError, "unknown post-class: #{post.class}"
-
end
-
end
-
-
def self.account_deletion(account_deletion)
-
DiasporaFederation::Entities::AccountDeletion.new(
-
author: account_deletion.diaspora_handle
-
)
-
end
-
-
def self.account_migration(account_migration)
-
DiasporaFederation::Entities::AccountMigration.new(
-
author: account_migration.sender.diaspora_handle,
-
profile: profile(account_migration.new_person.profile),
-
remote_photo_path: account_migration.remote_photo_path,
-
signature: account_migration.signature
-
)
-
end
-
-
def self.block(block)
-
DiasporaFederation::Entities::Contact.new(
-
author: block.user.diaspora_handle,
-
recipient: block.person.diaspora_handle,
-
sharing: false,
-
following: false,
-
blocking: Block.exists?(user: block.user, person: block.person)
-
)
-
end
-
-
def self.comment(comment)
-
DiasporaFederation::Entities::Comment.new(
-
{
-
author: comment.diaspora_handle,
-
guid: comment.guid,
-
parent_guid: comment.post.guid,
-
text: comment.text,
-
created_at: comment.created_at,
-
edited_at: comment.signature&.additional_data&.[]("edited_at"),
-
author_signature: comment.signature.try(:author_signature),
-
parent: related_entity(comment.post)
-
},
-
comment.signature.try(:order),
-
comment.signature.try(:additional_data) || {}
-
)
-
end
-
-
def self.contact(contact)
-
DiasporaFederation::Entities::Contact.new(
-
author: contact.user.diaspora_handle,
-
recipient: contact.person.diaspora_handle,
-
sharing: contact.receiving,
-
following: contact.receiving,
-
blocking: Block.exists?(user: contact.user, person: contact.person)
-
)
-
end
-
-
def self.conversation(conversation)
-
DiasporaFederation::Entities::Conversation.new(
-
author: conversation.diaspora_handle,
-
guid: conversation.guid,
-
subject: conversation.subject,
-
created_at: conversation.created_at,
-
participants: conversation.participant_handles,
-
messages: conversation.messages.map {|message| message(message) }
-
)
-
end
-
-
def self.like(like)
-
DiasporaFederation::Entities::Like.new(
-
{
-
author: like.diaspora_handle,
-
guid: like.guid,
-
parent_guid: like.target.guid,
-
positive: like.positive,
-
parent_type: Mappings.entity_name_for(like.target),
-
author_signature: like.signature.try(:author_signature),
-
parent: related_entity(like.target)
-
},
-
like.signature.try(:order),
-
like.signature.try(:additional_data) || {}
-
)
-
end
-
-
def self.location(location)
-
DiasporaFederation::Entities::Location.new(
-
address: location.address,
-
lat: location.lat,
-
lng: location.lng
-
)
-
end
-
-
def self.message(message)
-
DiasporaFederation::Entities::Message.new(
-
author: message.diaspora_handle,
-
guid: message.guid,
-
text: message.text,
-
created_at: message.created_at,
-
conversation_guid: message.conversation.guid
-
)
-
end
-
-
def self.participation(participation)
-
DiasporaFederation::Entities::Participation.new(
-
author: participation.diaspora_handle,
-
guid: participation.guid,
-
parent_guid: participation.target.guid,
-
parent_type: Mappings.entity_name_for(participation.target)
-
)
-
end
-
-
def self.photo(photo)
-
DiasporaFederation::Entities::Photo.new(
-
author: photo.diaspora_handle,
-
guid: photo.guid,
-
public: photo.public,
-
created_at: photo.created_at,
-
remote_photo_path: photo.remote_photo_path,
-
remote_photo_name: photo.remote_photo_name,
-
text: photo.text,
-
status_message_guid: photo.status_message_guid,
-
height: photo.height,
-
width: photo.width
-
)
-
end
-
-
def self.poll(poll)
-
DiasporaFederation::Entities::Poll.new(
-
guid: poll.guid,
-
question: poll.question,
-
poll_answers: poll.poll_answers.map {|answer| poll_answer(answer) }
-
)
-
end
-
-
def self.poll_answer(poll_answer)
-
DiasporaFederation::Entities::PollAnswer.new(
-
guid: poll_answer.guid,
-
answer: poll_answer.answer
-
)
-
end
-
-
def self.poll_participation(poll_participation)
-
DiasporaFederation::Entities::PollParticipation.new(
-
{
-
author: poll_participation.diaspora_handle,
-
guid: poll_participation.guid,
-
parent_guid: poll_participation.poll.guid,
-
poll_answer_guid: poll_participation.poll_answer.guid,
-
author_signature: poll_participation.signature.try(:author_signature),
-
parent: related_entity(poll_participation.poll)
-
},
-
poll_participation.signature.try(:order),
-
poll_participation.signature.try(:additional_data) || {}
-
)
-
end
-
-
def self.profile(profile)
-
DiasporaFederation::Entities::Profile.new(
-
author: profile.diaspora_handle,
-
edited_at: profile.updated_at,
-
first_name: profile.first_name,
-
last_name: profile.last_name,
-
image_url: profile.image_url,
-
image_url_medium: profile.image_url_medium,
-
image_url_small: profile.image_url_small,
-
birthday: profile.birthday,
-
gender: profile.gender,
-
bio: profile.bio,
-
location: profile.location,
-
searchable: profile.searchable,
-
nsfw: profile.nsfw,
-
tag_string: profile.tag_string,
-
public: profile.public_details
-
)
-
end
-
-
def self.reshare(reshare)
-
DiasporaFederation::Entities::Reshare.new(
-
root_author: reshare.root_diaspora_id,
-
root_guid: reshare.root_guid,
-
author: reshare.diaspora_handle,
-
guid: reshare.guid,
-
created_at: reshare.created_at
-
)
-
end
-
-
def self.retraction(retraction)
-
retraction.class.entity_class.new(retraction.data)
-
end
-
-
def self.status_message(status_message)
-
DiasporaFederation::Entities::StatusMessage.new(
-
author: status_message.diaspora_handle,
-
guid: status_message.guid,
-
text: status_message.text,
-
photos: status_message.photos.map {|photo| photo(photo) },
-
location: status_message.location ? location(status_message.location) : nil,
-
poll: status_message.poll ? poll(status_message.poll) : nil,
-
public: status_message.public,
-
created_at: status_message.created_at,
-
provider_display_name: status_message.provider_display_name
-
)
-
end
-
-
def self.related_entity(entity)
-
DiasporaFederation::Entities::RelatedEntity.new(
-
author: entity.author.diaspora_handle,
-
local: entity.author.local?,
-
public: entity.respond_to?(:public?) && entity.public?,
-
parent: entity.respond_to?(:parent) ? related_entity(entity.parent) : nil
-
)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
module Mappings
-
# rubocop:disable Metrics/CyclomaticComplexity
-
-
# used in Diaspora::Federation::Receive
-
def self.receiver_for(federation_entity)
-
case federation_entity
-
when DiasporaFederation::Entities::AccountMigration then :account_migration
-
when DiasporaFederation::Entities::Comment then :comment
-
when DiasporaFederation::Entities::Contact then :contact
-
when DiasporaFederation::Entities::Conversation then :conversation
-
when DiasporaFederation::Entities::Like then :like
-
when DiasporaFederation::Entities::Message then :message
-
when DiasporaFederation::Entities::Participation then :participation
-
when DiasporaFederation::Entities::Photo then :photo
-
when DiasporaFederation::Entities::PollParticipation then :poll_participation
-
when DiasporaFederation::Entities::Profile then :profile
-
when DiasporaFederation::Entities::Reshare then :reshare
-
when DiasporaFederation::Entities::StatusMessage then :status_message
-
else not_found(federation_entity.class)
-
end
-
end
-
-
# used in Diaspora::Federation::Entities
-
def self.builder_for(diaspora_entity)
-
case diaspora_entity
-
when AccountMigration then :account_migration
-
when AccountDeletion then :account_deletion
-
when Block then :block
-
when Comment then :comment
-
when Contact then :contact
-
when Conversation then :conversation
-
when Like then :like
-
when Message then :message
-
when Participation then :participation
-
when Photo then :photo
-
when PollParticipation then :poll_participation
-
when Profile then :profile
-
when Reshare then :reshare
-
when Retraction then :retraction
-
when ContactRetraction then :retraction
-
when StatusMessage then :status_message
-
else not_found(diaspora_entity.class)
-
end
-
end
-
-
def self.model_class_for(entity_name)
-
case entity_name
-
when "Comment" then Comment
-
when "Conversation" then Conversation
-
when "Like" then Like
-
when "Participation" then Participation
-
when "PollParticipation" then PollParticipation
-
when "Photo" then Photo
-
when "Poll" then Poll
-
when "Post" then Post
-
when "Person" then Person # TODO: deprecated
-
when "Reshare" then Post
-
when "StatusMessage" then Post
-
else not_found(entity_name)
-
end
-
end
-
-
def self.entity_name_for(model)
-
case model
-
when Comment then "Comment"
-
when Like then "Like"
-
when Participation then "Participation"
-
when PollParticipation then "PollParticipation"
-
when Photo then "Photo"
-
when Post then "Post"
-
else not_found(model.class)
-
end
-
end
-
# rubocop:enable Metrics/CyclomaticComplexity
-
-
private_class_method def self.not_found(key)
-
raise DiasporaFederation::Entity::UnknownEntity, "unknown entity: #{key}"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Federation
-
module Receive
-
extend Diaspora::Logging
-
-
def self.perform(entity, opts={})
-
public_send(Mappings.receiver_for(entity), entity, opts)
-
end
-
-
def self.handle_closed_recipient(sender, recipient)
-
return unless recipient.closed_account?
-
-
entity = recipient.person.account_migration || recipient.person.account_deletion
-
Diaspora::Federation::Dispatcher.build(recipient, entity, subscribers: [sender]).dispatch if entity.present?
-
-
raise Diaspora::Federation::RecipientClosed
-
end
-
-
def self.account_deletion(entity)
-
person = author_of(entity)
-
AccountDeletion.create!(person: person) unless AccountDeletion.where(person: person).exists?
-
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
-
raise e unless AccountDeletion.where(person: person).exists?
-
logger.warn "ignoring error on receive AccountDeletion:#{entity.author}: #{e.class}: #{e.message}"
-
end
-
-
def self.account_migration(entity, opts)
-
old_person = author_of(entity)
-
profile = profile(entity.profile, opts)
-
return if AccountMigration.exists?(old_person: old_person, new_person: profile.person)
-
-
AccountMigration.create!(
-
old_person: old_person,
-
new_person: profile.person,
-
remote_photo_path: entity.remote_photo_path
-
).tap do |migration|
-
migration.signature = entity.signature if old_person.local?
-
migration.save!
-
end
-
rescue StandardError => e
-
raise e unless AccountMigration.exists?(old_person: old_person, new_person: profile.person)
-
-
logger.warn "ignoring error on receive #{entity}: #{e.class}: #{e.message}"
-
nil
-
end
-
-
def self.comment(entity, opts)
-
receive_relayable(Comment, entity, opts) do
-
Comment.new(
-
author: author_of(entity),
-
guid: entity.guid,
-
created_at: entity.created_at,
-
text: entity.text,
-
commentable: Post.find_by(guid: entity.parent_guid)
-
)
-
end
-
end
-
-
def self.contact(entity, _opts)
-
recipient = Person.find_by(diaspora_handle: entity.recipient).owner
-
if entity.sharing
-
Contact.create_or_update_sharing_contact(recipient, author_of(entity))
-
else
-
recipient.disconnected_by(author_of(entity))
-
nil
-
end
-
end
-
-
def self.conversation(entity, _opts)
-
author = author_of(entity)
-
ignore_existing_guid(Conversation, entity.guid, author) do
-
Conversation.create!(
-
author: author,
-
guid: entity.guid,
-
subject: entity.subject,
-
created_at: entity.created_at,
-
participant_handles: entity.participants,
-
messages: entity.messages.map {|message| build_message(message) }
-
)
-
end
-
end
-
-
def self.like(entity, opts)
-
receive_relayable(Like, entity, opts) do
-
Like.new(
-
author: author_of(entity),
-
guid: entity.guid,
-
positive: entity.positive,
-
target: Mappings.model_class_for(entity.parent_type).find_by(guid: entity.parent_guid)
-
)
-
end
-
end
-
-
def self.message(entity, _opts)
-
ignore_existing_guid(Message, entity.guid, author_of(entity)) do
-
build_message(entity).tap(&:save!)
-
end
-
end
-
-
def self.participation(entity, _opts)
-
author = author_of(entity)
-
ignore_existing_guid(Participation, entity.guid, author) do
-
Participation.create!(
-
author: author,
-
guid: entity.guid,
-
target: Mappings.model_class_for(entity.parent_type).find_by(guid: entity.parent_guid)
-
)
-
end
-
end
-
-
def self.photo(entity, _opts)
-
author = author_of(entity)
-
persisted_photo = load_from_database(Photo, entity.guid, author)
-
-
if persisted_photo
-
persisted_photo.tap do |photo|
-
photo.update(
-
text: entity.text,
-
public: entity.public,
-
created_at: entity.created_at,
-
remote_photo_path: entity.remote_photo_path,
-
remote_photo_name: entity.remote_photo_name,
-
status_message_guid: entity.status_message_guid,
-
height: entity.height,
-
width: entity.width
-
)
-
end
-
else
-
save_photo(entity)
-
end
-
end
-
-
def self.poll_participation(entity, opts)
-
receive_relayable(PollParticipation, entity, opts) do
-
PollParticipation.new(
-
author: author_of(entity),
-
guid: entity.guid,
-
poll: Poll.find_by(guid: entity.parent_guid),
-
poll_answer_guid: entity.poll_answer_guid
-
)
-
end
-
end
-
-
def self.profile(entity, _opts)
-
author_of(entity).profile.tap do |profile|
-
profile.update(
-
first_name: entity.first_name,
-
last_name: entity.last_name,
-
image_url: entity.image_url,
-
image_url_medium: entity.image_url_medium,
-
image_url_small: entity.image_url_small,
-
birthday: entity.birthday,
-
gender: entity.gender,
-
bio: entity.bio,
-
location: entity.location,
-
searchable: entity.searchable,
-
nsfw: entity.nsfw,
-
tag_string: entity.tag_string,
-
public_details: entity.public
-
)
-
end
-
end
-
-
def self.reshare(entity, _opts)
-
author = author_of(entity)
-
ignore_existing_guid(Reshare, entity.guid, author) do
-
Reshare.create!(
-
author: author,
-
guid: entity.guid,
-
created_at: entity.created_at,
-
root_guid: entity.root_guid
-
).tap {|reshare| send_participation_for(reshare) }
-
end
-
end
-
-
def self.retraction(entity, recipient_id)
-
model_class = Diaspora::Federation::Mappings.model_class_for(entity.target_type)
-
object = model_class.where(guid: entity.target_guid).take!
-
-
case object
-
when Person
-
User.find(recipient_id).disconnected_by(object)
-
when Diaspora::Relayable
-
if object.root.author.local?
-
root_author = object.root.author.owner
-
retraction = Retraction.for(object)
-
retraction.defer_dispatch(root_author, false)
-
retraction.perform
-
else
-
object.destroy!
-
end
-
else
-
object.destroy!
-
end
-
end
-
-
def self.status_message(entity, _opts) # rubocop:disable Metrics/AbcSize
-
try_load_existing_guid(StatusMessage, entity.guid, author_of(entity)) do
-
StatusMessage.new(
-
author: author_of(entity),
-
guid: entity.guid,
-
text: entity.text,
-
public: entity.public,
-
created_at: entity.created_at,
-
provider_display_name: entity.provider_display_name
-
).tap do |status_message|
-
status_message.location = build_location(entity.location) if entity.location
-
status_message.poll = build_poll(entity.poll) if entity.poll
-
status_message.photos = save_or_load_photos(entity.photos)
-
-
status_message.save!
-
-
send_participation_for(status_message)
-
end
-
end
-
end
-
-
private_class_method def self.author_of(entity)
-
Person.by_account_identifier(entity.author)
-
end
-
-
private_class_method def self.build_location(entity)
-
Location.new(
-
address: entity.address,
-
lat: entity.lat,
-
lng: entity.lng
-
)
-
end
-
-
private_class_method def self.build_message(entity)
-
Message.new(
-
author: author_of(entity),
-
guid: entity.guid,
-
text: entity.text,
-
created_at: entity.created_at,
-
conversation_guid: entity.conversation_guid
-
)
-
end
-
-
private_class_method def self.build_poll(entity)
-
Poll.new(
-
guid: entity.guid,
-
question: entity.question
-
).tap do |poll|
-
poll.poll_answers = entity.poll_answers.map do |answer|
-
PollAnswer.new(
-
guid: answer.guid,
-
answer: answer.answer,
-
poll: poll
-
)
-
end
-
end
-
end
-
-
private_class_method def self.save_photo(entity)
-
Photo.create!(
-
author: author_of(entity),
-
guid: entity.guid,
-
text: entity.text,
-
public: entity.public,
-
created_at: entity.created_at,
-
remote_photo_path: entity.remote_photo_path,
-
remote_photo_name: entity.remote_photo_name,
-
status_message_guid: entity.status_message_guid,
-
height: entity.height,
-
width: entity.width
-
)
-
end
-
-
private_class_method def self.save_or_load_photos(photos)
-
photos.map do |photo|
-
try_load_existing_guid(Photo, photo.guid, author_of(photo)) { save_photo(photo) }
-
end
-
end
-
-
private_class_method def self.receive_relayable(klass, entity, opts)
-
save_relayable(klass, entity) { yield }
-
.tap {|relayable| relay_relayable(relayable) if relayable && !opts[:skip_relaying] }
-
end
-
-
private_class_method def self.save_relayable(klass, entity)
-
ignore_existing_guid(klass, entity.guid, author_of(entity)) do
-
yield.tap do |relayable|
-
retract_if_author_ignored(relayable)
-
-
relayable.signature = build_signature(klass, entity) if relayable.root.author.local?
-
relayable.save!
-
end
-
end
-
end
-
-
# This are property names that are known by the +diaspora_federation+ library as properties but not
-
# specially stored in our database and therefore need to be stored in the +additional_data+ field.
-
UNKNOWN_PROPERTIES_NAMES = %i[edited_at].freeze
-
private_constant :UNKNOWN_PROPERTIES_NAMES
-
-
private_class_method def self.build_signature(klass, entity)
-
special_additional_data = UNKNOWN_PROPERTIES_NAMES.map {|name|
-
[name.to_s, entity.public_send(name)] if entity.respond_to?(name) && entity.signature_order.include?(name)
-
}.compact.to_h
-
-
klass.reflect_on_association(:signature).klass.new(
-
author_signature: entity.author_signature,
-
additional_data: entity.additional_data.merge(special_additional_data),
-
signature_order: SignatureOrder.find_or_create_by!(order: entity.signature_order.join(" "))
-
)
-
end
-
-
private_class_method def self.retract_if_author_ignored(relayable)
-
root_author = relayable.root.author.owner
-
return unless root_author && root_author.ignored_people.include?(relayable.author)
-
-
retraction = Retraction.for(relayable)
-
Diaspora::Federation::Dispatcher.build(root_author, retraction, subscribers: [relayable.author]).dispatch
-
-
raise Diaspora::Federation::AuthorIgnored
-
end
-
-
private_class_method def self.relay_relayable(relayable)
-
root_author = relayable.root.author.owner
-
Diaspora::Federation::Dispatcher.defer_dispatch(root_author, relayable) if root_author
-
end
-
-
# check if the object already exists, otherwise save it.
-
# if save fails (probably because of a second object received parallel),
-
# check again if an object with the same guid already exists, but maybe has a different author.
-
# @raise [InvalidAuthor] if the author of the existing object doesn't match
-
private_class_method def self.ignore_existing_guid(klass, guid, author)
-
yield unless klass.where(guid: guid, author_id: author.id).exists?
-
rescue => e
-
raise e unless load_from_database(klass, guid, author)
-
logger.warn "ignoring error on receive #{klass}:#{guid}: #{e.class}: #{e.message}"
-
nil
-
end
-
-
# try to load the object first from the DB and if not available, save it.
-
# if save fails (probably because of a second object received parallel),
-
# try again to load it, because it is possibly there now.
-
# @raise [InvalidAuthor] if the author of the existing object doesn't match
-
private_class_method def self.try_load_existing_guid(klass, guid, author)
-
load_from_database(klass, guid, author) || yield
-
rescue Diaspora::Federation::InvalidAuthor => e
-
raise e # don't try loading from db twice
-
rescue => e
-
logger.warn "failed to save #{klass}:#{guid} (#{e.class}: #{e.message}) - try loading it from DB"
-
load_from_database(klass, guid, author).tap do |object|
-
raise e unless object
-
end
-
end
-
-
# @raise [InvalidAuthor] if the author of the loaded object doesn't match
-
private_class_method def self.load_from_database(klass, guid, author)
-
klass.find_by(guid: guid).tap do |object|
-
if object && object.author_id != author.id
-
raise Diaspora::Federation::InvalidAuthor, "#{klass}:#{guid}: #{author.diaspora_handle}"
-
end
-
end
-
end
-
-
private_class_method def self.send_participation_for(post)
-
return unless post.public?
-
user = user_for_participation
-
participation = Participation.new(target: post, author: user.person)
-
Diaspora::Federation::Dispatcher.build(user, participation, subscribers: [post.author]).dispatch
-
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
-
logger.warn "failed to send participation for post #{post.guid}: #{e.class}: #{e.message}"
-
end
-
-
# Use configured admin account if available,
-
# or use first user with admin role if available,
-
# or use first user who isn't closed
-
private_class_method def self.user_for_participation
-
User.find_by(username: AppConfig.admins.account.to_s) ||
-
Role.admins.first&.person&.owner ||
-
User.where(locked_at: nil).first
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Fetcher
-
require 'diaspora/fetcher/public'
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2012, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
module Diaspora; module Fetcher; class Public
-
include Diaspora::Logging
-
-
# various states that can be assigned to a person to describe where
-
# in the process of fetching their public posts we're currently at
-
Status_Initial = 0
-
Status_Running = 1
-
Status_Fetched = 2
-
Status_Processed = 3
-
Status_Done = 4
-
Status_Failed = 5
-
Status_Unfetchable = 6
-
-
def self.queue_for(person)
-
Workers::FetchPublicPosts.perform_async(person.diaspora_handle) unless person.fetch_status > Status_Initial
-
end
-
-
# perform all actions necessary to fetch the public posts of a person
-
# with the given diaspora_id
-
def fetch! diaspora_id
-
@person = Person.by_account_identifier diaspora_id
-
return unless qualifies_for_fetching?
-
-
begin
-
retrieve_and_process_posts
-
rescue => e
-
set_fetch_status Public::Status_Failed
-
raise e
-
end
-
-
set_fetch_status Public::Status_Done
-
end
-
-
private
-
# checks, that public posts for the person can be fetched,
-
# if it is reasonable to do so, and that they have not been fetched already
-
def qualifies_for_fetching?
-
raise ActiveRecord::RecordNotFound unless @person.present?
-
return false if @person.fetch_status == Public::Status_Unfetchable
-
-
# local users don't need to be fetched
-
if @person.local?
-
set_fetch_status Public::Status_Unfetchable
-
return false
-
end
-
-
# this record is already being worked on
-
return false if @person.fetch_status > Public::Status_Initial
-
-
# ok, let's go
-
@person.remote? &&
-
@person.fetch_status == Public::Status_Initial
-
end
-
-
# call the methods to fetch and process the public posts for the person
-
# does some error logging, in case of an exception
-
def retrieve_and_process_posts
-
begin
-
retrieve_posts
-
rescue => e
-
logger.error "unable to retrieve public posts for #{@person.diaspora_handle}"
-
raise e
-
end
-
-
begin
-
process_posts
-
rescue => e
-
logger.error "unable to process public posts for #{@person.diaspora_handle}"
-
raise e
-
end
-
end
-
-
# fetch the public posts of the person from their server and save the
-
# JSON response to `@data`
-
def retrieve_posts
-
set_fetch_status Public::Status_Running
-
-
logger.info "fetching public posts for #{@person.diaspora_handle}"
-
-
resp = Faraday.get("#{@person.url}people/#{@person.guid}/stream") do |req|
-
req.headers['Accept'] = 'application/json'
-
req.headers['User-Agent'] = 'diaspora-fetcher'
-
end
-
-
logger.debug "fetched response: #{resp.body.to_s[0..250]}"
-
-
@data = JSON.parse resp.body
-
set_fetch_status Public::Status_Fetched
-
end
-
-
# process the public posts that were previously fetched with `retrieve_posts`
-
# adds posts, which pass some basic sanity-checking
-
# @see validate
-
def process_posts
-
@data.each do |post|
-
next unless validate(post)
-
-
logger.info "saving fetched post (#{post['guid']}) to database"
-
-
logger.debug "post: #{post.to_s[0..250]}"
-
-
DiasporaFederation::Federation::Fetcher.fetch_public(
-
@person.diaspora_handle,
-
:post,
-
post["guid"]
-
)
-
rescue DiasporaFederation::Federation::Fetcher::NotFetchable => e
-
logger.warn e.message
-
end
-
set_fetch_status Public::Status_Processed
-
end
-
-
# set and save the fetch status for the current person
-
def set_fetch_status status
-
return if @person.nil?
-
-
@person.fetch_status = status
-
@person.save
-
end
-
-
# perform various validations to make sure the post can be saved without
-
# troubles
-
# @see check_existing
-
# @see check_author
-
# @see check_public
-
def validate post
-
check_existing(post) && check_author(post) && check_public(post)
-
end
-
-
# hopefully there is no post with the same guid somewhere already...
-
def check_existing post
-
new_post = (Post.find_by_guid(post['guid']).blank?)
-
-
logger.warn "a post with that guid (#{post['guid']}) already exists" unless new_post
-
-
new_post
-
end
-
-
# checks if the author of the given post is actually from the person
-
# we're currently processing
-
def check_author post
-
guid = post['author']['guid']
-
equal = (guid == @person.guid)
-
-
unless equal
-
logger.warn "the author (#{guid}) does not match the person currently being processed (#{@person.guid})"
-
end
-
-
equal
-
end
-
-
# returns wether the given post is public
-
def check_public post
-
ispublic = (post['public'] == true)
-
-
logger.warn "the post (#{post['guid']}) is not public, this is not intended..." unless ispublic
-
-
ispublic
-
end
-
end; end; end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module Fields
-
1
module Author
-
1
def self.included(model)
-
8
model.class_eval do
-
8
belongs_to :author, class_name: "Person"
-
-
8
delegate :diaspora_handle, to: :author
-
-
8
validates :author, presence: true
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Fields
-
module Guid
-
# Creates a after_initialize callback which calls #set_guid
-
def self.included(model)
-
model.class_eval do
-
after_initialize :set_guid
-
validates :guid, uniqueness: true
-
end
-
end
-
-
# @return [String] The model's guid.
-
def set_guid
-
self.guid = UUID.generate(:compact) if guid.blank?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module Fields
-
1
module Target
-
1
def self.included(model)
-
2
model.class_eval do
-
2
belongs_to :target, polymorphic: true
-
-
2
validates :target_id, uniqueness: {scope: %i(target_type author_id)}
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Diaspora
-
1
module Likeable
-
1
def self.included(model)
-
2
model.instance_eval do
-
1329
has_many :likes, -> { where(positive: true) }, dependent: :delete_all, as: :target
-
54
has_many :dislikes, -> { where(positive: false) }, class_name: 'Like', dependent: :delete_all, as: :target
-
end
-
end
-
-
# @return [Integer]
-
1
def update_likes_counter
-
432
self.class.where(id: self.id).
-
update_all(likes_count: self.likes.count)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# a logging mixin providing the logger
-
module Diaspora
-
module Logging
-
private
-
-
def logger
-
@logger ||= ::Logging::Logger[self]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Markdownify
-
require 'diaspora/markdownify/html'
-
require 'diaspora/markdownify/email'
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Markdownify
-
class Email < Redcarpet::Render::HTML
-
def preprocess(text)
-
Diaspora::Taggable.format_tags_for_mail text
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
module Markdownify
-
class HTML < Redcarpet::Render::HTML
-
include ActionView::Helpers::TextHelper
-
-
def autolink link, type
-
Twitter::TwitterText::Autolink.auto_link_urls(
-
link,
-
url_target: "_blank",
-
link_attribute_block: lambda {|_, attr| attr[:rel] += " noopener noreferrer" }
-
)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora::Mentionable
-
-
# regex for finding mention markup in plain text:
-
# "message @{user@pod.net} text"
-
# it can also contain a name, which gets used as the link text:
-
# "message @{User Name; user@pod.net} text"
-
# will yield "User Name" and "user@pod.net"
-
REGEX = /@\{(?:([^\}]+?); )?([^\} ]+)\}/
-
-
# class attribute that will be added to all mention html links
-
PERSON_HREF_CLASS = "mention hovercardable"
-
-
def self.mention_attrs(mention_str)
-
name, diaspora_id = mention_str.match(REGEX).captures
-
-
[name.try(:strip).presence, diaspora_id.strip]
-
end
-
-
# takes a message text and returns the text with mentions in (html escaped)
-
# plain text or formatted with html markup linking to user profiles.
-
# default is html output.
-
#
-
# @param [String] text containing mentions
-
# @param [Array<Person>] list of mentioned people
-
# @param [Hash] formatting options
-
# @return [String] formatted message
-
def self.format(msg_text, people, opts={})
-
people = [*people]
-
-
msg_text.to_s.gsub(REGEX) {|match_str|
-
name, diaspora_id = mention_attrs(match_str)
-
person = people.find {|p| p.diaspora_handle == diaspora_id }
-
-
"@#{ERB::Util.h(MentionsInternal.mention_link(person, name, diaspora_id, opts))}"
-
}
-
end
-
-
# takes a message text and returns an array of people constructed from the
-
# contained mentions
-
#
-
# @param [String] text containing mentions
-
# @return [Array<Person>] array of people
-
def self.people_from_string(msg_text)
-
identifiers = msg_text.to_s.scan(REGEX).map {|match_str| match_str.second.strip }
-
-
identifiers.compact.uniq.map {|identifier| find_or_fetch_person_by_identifier(identifier) }.compact
-
end
-
-
# takes a message text and converts mentions for people that are not in the
-
# given array to simple markdown links, leaving only mentions for people who
-
# will actually be able to receive notifications for being mentioned.
-
#
-
# @param [String] message text
-
# @param [Array] allowed_people ids of people that are allowed to stay
-
# @param [Boolean] absolute_links (false) render mentions with absolute links
-
# @return [String] message text with filtered mentions
-
def self.filter_people(msg_text, allowed_people, absolute_links: false)
-
mentioned_ppl = people_from_string(msg_text)
-
-
msg_text.to_s.gsub(REGEX) {|match_str|
-
name, diaspora_id = mention_attrs(match_str)
-
person = mentioned_ppl.find {|p| p.diaspora_handle == diaspora_id }
-
-
if person && allowed_people.include?(person.id)
-
match_str
-
else
-
"@#{MentionsInternal.profile_link(person, name, diaspora_id, absolute: absolute_links)}"
-
end
-
}
-
end
-
-
# Escapes special chars in mentions to not be parsed as markdown
-
#
-
# @param [String] text containing mentions
-
# @return [String] escaped message
-
def self.escape_for_markdown(msg_text)
-
msg_text.to_s.gsub(REGEX) {|match_str|
-
match_str.gsub("_", "\\_")
-
}
-
end
-
-
private_class_method def self.find_or_fetch_person_by_identifier(identifier)
-
Person.find_or_fetch_by_identifier(identifier) if Validation::Rule::DiasporaId.new.valid_value?(identifier)
-
rescue DiasporaFederation::Discovery::DiscoveryError
-
nil
-
end
-
-
# inline module for namespacing
-
module MentionsInternal
-
extend ERB::Util
-
-
# output a formatted mention link as defined by the given arguments.
-
# if the display name is blank, falls back to the person's name.
-
# @see Diaspora::Mentions#format
-
#
-
# @param [Person] AR Person
-
# @param [String] display name
-
# @param [Hash] formatting options
-
def self.mention_link(person, display_name, diaspora_id, opts)
-
return display_name || diaspora_id unless person.present?
-
-
display_name ||= person.name
-
if opts[:plain_text]
-
display_name
-
else
-
# rubocop:disable Rails/OutputSafety
-
remote_or_hovercard_link = Rails.application.routes.url_helpers.person_path(person).html_safe
-
"<a data-hovercard=\"#{remote_or_hovercard_link}\" href=\"#{remote_or_hovercard_link}\" " \
-
"class=\"#{PERSON_HREF_CLASS}\">#{html_escape_once(display_name)}</a>".html_safe
-
# rubocop:enable Rails/OutputSafety
-
end
-
end
-
-
# output a markdown formatted link to the given person with the display name as the link text.
-
# if the display name is blank, falls back to the person's name.
-
#
-
# @param [Person] AR Person
-
# @param [String] display name
-
# @param [String] diaspora_id
-
# @param [Boolean] absolute (false) render absolute link
-
# @return [String] markdown person link
-
def self.profile_link(person, display_name, diaspora_id, absolute: false)
-
return display_name || diaspora_id unless person.present?
-
-
url_helper = Rails.application.routes.url_helpers
-
"[#{display_name || person.name}](#{absolute ? url_helper.person_url(person) : url_helper.person_path(person)})"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module MentionsContainer
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
2
after_create :create_mentions
-
2
has_many :mentions, as: :mentions_container, dependent: :destroy
-
end
-
-
1
def mentioned_people
-
2873
if persisted?
-
2860
mentions.includes(person: :profile).map(&:person)
-
else
-
13
Diaspora::Mentionable.people_from_string(text)
-
end
-
end
-
-
1
def add_mention_subscribers?
-
1397
public?
-
end
-
-
1
def subscribers
-
1397
super.tap {|subscribers|
-
1397
subscribers.concat(mentions.map(&:person).select(&:remote?)) if add_mention_subscribers?
-
}
-
end
-
-
1
def create_mentions
-
3289
Diaspora::Mentionable.people_from_string(text).each do |person|
-
345
mentions.find_or_create_by(person_id: person.id)
-
end
-
end
-
-
1
def message
-
8299
@message ||= Diaspora::MessageRenderer.new text, mentioned_people: mentioned_people
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Diaspora
-
# Takes a raw message text and converts it to
-
# various desired target formats, respecting
-
# all possible formatting options supported
-
# by Diaspora
-
class MessageRenderer
-
class Processor
-
class << self
-
private :new
-
-
def process message, options, &block
-
return '' if message.blank? # Optimize for empty message
-
processor = new message, options
-
processor.instance_exec(&block)
-
processor.message
-
end
-
-
def normalize message
-
message.gsub(/[\u202a\u202b]#[\u200e\u200f\u202d\u202e](\S+)\u202c/u, "#\\1")
-
end
-
end
-
-
attr_reader :message, :options
-
-
def initialize message, options
-
@message = message
-
@options = options
-
end
-
-
def squish
-
@message = message.squish if options[:squish]
-
end
-
-
def append_and_truncate
-
if options[:truncate]
-
# TODO: Remove .dup when upgrading to Rails 6.x.
-
@message = @message.truncate(options[:truncate] - options[:append].to_s.size).dup
-
end
-
-
@message << options[:append].to_s
-
@message << options[:append_after_truncate].to_s
-
end
-
-
def escape
-
if options[:escape]
-
@message = ERB::Util.html_escape_once message
-
end
-
end
-
-
def strip_markdown
-
# Footnotes are not supported in text-only outputs (mail, crossposts etc)
-
stripdown_options = options[:markdown_options].except(:footnotes)
-
renderer = Redcarpet::Markdown.new Redcarpet::Render::StripDown, stripdown_options
-
@message = renderer.render(message).strip
-
end
-
-
def markdownify(renderer_class=Diaspora::Markdownify::HTML)
-
renderer = renderer_class.new options[:markdown_render_options]
-
markdown = Redcarpet::Markdown.new renderer, options[:markdown_options]
-
-
@message = markdown.render message
-
end
-
-
# In very clear cases, let newlines become <br /> tags
-
# Grabbed from Github flavored Markdown
-
def process_newlines
-
message.gsub(/^[\w\<][^\n]*\n+/) do |x|
-
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
-
end
-
end
-
-
def escape_mentions_for_markdown
-
@message = Diaspora::Mentionable.escape_for_markdown(message)
-
end
-
-
def render_mentions
-
unless options[:disable_hovercards] || options[:mentioned_people].empty?
-
@message = Diaspora::Mentionable.format message, options[:mentioned_people]
-
end
-
-
if options[:disable_hovercards]
-
@message = Diaspora::Mentionable.filter_people(message, [], absolute_links: true)
-
else
-
make_mentions_plain_text
-
end
-
end
-
-
def make_mentions_plain_text
-
@message = Diaspora::Mentionable.format message, options[:mentioned_people], plain_text: true
-
end
-
-
def render_tags
-
@message = Diaspora::Taggable.format_tags message, no_escape: !options[:escape_tags]
-
end
-
-
def camo_urls
-
@message = Diaspora::Camo.from_markdown(@message)
-
end
-
-
def normalize
-
@message = self.class.normalize(@message)
-
end
-
-
def diaspora_links
-
@message = @message.gsub(DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX) {|match_str|
-
guid = Regexp.last_match(3)
-
Regexp.last_match(2) == "post" && Post.exists?(guid: guid) ? AppConfig.url_to("/posts/#{guid}") : match_str
-
}
-
end
-
end
-
-
DEFAULTS = {mentioned_people: [],
-
disable_hovercards: false,
-
truncate: false,
-
append: nil,
-
append_after_truncate: nil,
-
squish: false,
-
escape: true,
-
escape_tags: false,
-
markdown_options: {
-
autolink: true,
-
fenced_code_blocks: true,
-
space_after_headers: true,
-
strikethrough: true,
-
footnotes: true,
-
tables: true,
-
no_intra_emphasis: true
-
},
-
markdown_render_options: {
-
filter_html: true,
-
hard_wrap: true,
-
safe_links_only: true
-
}}.freeze
-
-
delegate :empty?, :blank?, :present?, to: :raw
-
-
# @param [String] text Raw input text
-
# @param [Hash] opts Global options affecting output
-
# @option opts [Array<Person>] :mentioned_people ([]) List of people
-
# allowed to mention
-
# @option opts [Boolean] :disable_hovercards (true) Render all mentions
-
# as absolute profile links. This ignores :mentioned_people
-
# @option opts [#to_i, Boolean] :truncate (false) Truncate message to
-
# the specified length
-
# @option opts [String] :append (nil) Append text to the end of
-
# the (truncated) message, counts into truncation length
-
# @option opts [String] :append_after_truncate (nil) Append text to the end
-
# of the (truncated) message, doesn't count into the truncation length
-
# @option opts [Boolean] :squish (false) Squish the message, that is
-
# remove all surrounding and consecutive whitespace
-
# @option opts [Boolean] :escape (true) Escape HTML relevant characters
-
# in the message. Note that his option is ignored in the plaintext
-
# renderers.
-
# @option opts [Boolean] :escape_tags (false) Escape HTML relevant
-
# characters in tags when rendering them
-
# @option opts [Hash] :markdown_options Override default options passed
-
# to Redcarpet
-
# @option opts [Hash] :markdown_render_options Override default options
-
# passed to the Redcarpet renderer
-
def initialize(text, opts={})
-
@text = text
-
@options = DEFAULTS.deep_merge opts
-
end
-
-
# @param [Hash] opts Override global output options, see {#initialize}
-
def plain_text opts={}
-
process(opts) {
-
make_mentions_plain_text
-
diaspora_links
-
squish
-
append_and_truncate
-
}
-
end
-
-
# @param [Hash] opts Override global output options, see {#initialize}
-
def plain_text_without_markdown opts={}
-
process(opts) {
-
make_mentions_plain_text
-
diaspora_links
-
strip_markdown
-
squish
-
append_and_truncate
-
}
-
end
-
-
# @param [Hash] opts Override global output options, see {#initialize}
-
def plain_text_for_json opts={}
-
process(opts) {
-
normalize
-
diaspora_links
-
camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
-
}
-
end
-
-
# @param [Hash] opts Override global output options, see {#initialize}
-
def html opts={}
-
process(opts) {
-
escape
-
normalize
-
diaspora_links
-
render_mentions
-
render_tags
-
squish
-
append_and_truncate
-
}.html_safe # rubocop:disable Rails/OutputSafety
-
end
-
-
# @param [Hash] opts Override global output options, see {#initialize}
-
def markdownified opts={}
-
process(opts) {
-
process_newlines
-
normalize
-
diaspora_links
-
camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
-
escape_mentions_for_markdown
-
markdownify
-
render_mentions
-
render_tags
-
squish
-
append_and_truncate
-
}.html_safe # rubocop:disable Rails/OutputSafety
-
end
-
-
def markdownified_for_mail
-
process(disable_hovercards: true) {
-
process_newlines
-
normalize
-
diaspora_links
-
camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
-
render_mentions
-
markdownify(Diaspora::Markdownify::Email)
-
squish
-
append_and_truncate
-
}.html_safe # rubocop:disable Rails/OutputSafety
-
end
-
-
# Get a short summary of the message
-
# @param [Hash] opts Additional options
-
# @option opts [Integer] :length (70) Truncate the title to
-
# this length. If not given defaults to 70.
-
def title opts={}
-
# Setext-style header
-
heading = if /\A(?<setext_content>.{1,200})\n(?:={1,200}|-{1,200})(?:\r?\n|$)/ =~ @text.lstrip
-
setext_content
-
# Atx-style header
-
elsif /\A\#{1,6}\s+(?<atx_content>.{1,200}?)(?:\s+#+)?(?:\r?\n|$)/ =~ @text.lstrip
-
atx_content
-
end
-
-
heading &&= self.class.new(heading).plain_text_without_markdown
-
-
if heading
-
heading.truncate opts.fetch(:length, 70)
-
else
-
plain_text_without_markdown squish: true, truncate: opts.fetch(:length, 70)
-
end
-
end
-
-
# Extracts all the urls from the raw message and return them in the form of a string
-
# Different URLs are seperated with a space
-
def urls
-
@urls ||= Twitter::TwitterText::Extractor.extract_urls(plain_text_without_markdown).map {|url|
-
Addressable::URI.parse(url).normalize.to_s
-
}
-
end
-
-
def raw
-
@text
-
end
-
-
def to_s
-
plain_text
-
end
-
-
private
-
-
def process(opts, &block)
-
Processor.process(@text, @options.deep_merge(opts), &block)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Diaspora
-
1
module Relayable
-
1
def self.included(model)
-
3
model.class_eval do
-
3
validates :parent, presence: true
-
3
validates_associated :parent
-
3
validate :author_is_not_ignored
-
-
3
delegate :public?, to: :parent
-
3
delegate :author, :diaspora_handle, to: :parent, prefix: true
-
end
-
end
-
-
1
def root
-
3792
@root ||= parent
-
3792
@root = @root.parent while @root.is_a?(Relayable)
-
3792
@root
-
end
-
-
1
def author_is_not_ignored
-
1236
unless new_record? && root.present? && root.author.local? &&
-
root.author.owner.ignored_people.include?(author)
-
1231
return
-
end
-
-
5
errors.add(:author_id, "This relayable author is ignored by the post author")
-
end
-
-
# @return [Array<Person>]
-
1
def subscribers
-
89
if root.author.local?
-
83
if author.local?
-
46
root.subscribers
-
else
-
58
root.subscribers.select(&:remote?).reject {|person| person.pod_id == author.pod_id }
-
end
-
else
-
6
[root.author, author]
-
end
-
end
-
-
1
def sender_for_dispatch
-
38
root.author.owner if root.author.local?
-
end
-
-
# @abstract
-
1
def parent
-
raise NotImplementedError.new('you must override parent in order to enable relayable on this model')
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
# the point of this object is to centralize the simmilarities of Photo and Post,
-
# as they used to be the same class
-
1
module Diaspora
-
1
module Shareable
-
1
def self.included(model)
-
2
model.instance_eval do
-
2
include Diaspora::Fields::Guid
-
2
include Diaspora::Fields::Author
-
-
2
has_many :aspect_visibilities, as: :shareable, validate: false, dependent: :delete_all
-
2
has_many :aspects, through: :aspect_visibilities
-
-
2
has_many :share_visibilities, as: :shareable, dependent: :delete_all
-
-
2
delegate :id, :name, :first_name, to: :author, prefix: true
-
-
# scopes
-
2
scope :with_visibility, -> {
-
189
joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = #{table_name}.id AND "\
-
"share_visibilities.shareable_type = '#{base_class}'")
-
}
-
-
2
scope :with_aspects, -> {
-
54
joins("LEFT OUTER JOIN aspect_visibilities ON aspect_visibilities.shareable_id = #{table_name}.id AND "\
-
" aspect_visibilities.shareable_type = '#{base_class}'")
-
}
-
end
-
2
model.extend Diaspora::Shareable::QueryMethods
-
end
-
-
1
def receive(recipient_user_ids)
-
1491
return if recipient_user_ids.empty? || public?
-
-
795
ShareVisibility.batch_import(recipient_user_ids, self)
-
end
-
-
# The list of people that should receive this Shareable.
-
#
-
# @return [Array<Person>] The list of subscribers to this shareable
-
1
def subscribers
-
1826
user = author.owner
-
1826
if public?
-
765
[*user.contact_people, author]
-
else
-
1061
user.people_in_aspects(user.aspects_with_shareable(self.class, id))
-
end
-
end
-
-
# Remote pods which are known to be subscribed to the post. Must include all pods which received the post in the
-
# past.
-
#
-
# @return [Array<String>] The list of pods' URIs
-
1
def subscribed_pods_uris
-
20
Pod.find(subscribers.select(&:remote?).map(&:pod_id).uniq).map {|pod| pod.url_to("") }
-
end
-
-
1
module QueryMethods
-
1
def owned_or_visible_by_user(user)
-
64
with_visibility.where(
-
visible_by_user(user).or(arel_table[:public].eq(true)
-
.or(arel_table[:author_id].eq(user.person_id)))
-
).select("DISTINCT #{table_name}.*")
-
end
-
-
1
def from_person_visible_by_user(user, person)
-
64
return owned_by_user(user) if person == user.person
-
-
43
with_visibility.where(author_id: person.id).where(
-
visible_by_user(user).or(arel_table[:public].eq(true))
-
).select("DISTINCT #{table_name}.*")
-
end
-
-
1
def for_visible_shareable_sql(max_time, order, limit=15, types=Stream::Base::TYPES_OF_POST_IN_STREAM)
-
139
by_max_time(max_time, order).order(table_name + ".id DESC").where(type: types).limit(limit)
-
end
-
-
1
def by_max_time(max_time, order="created_at")
-
182
where("#{table_name}.#{order} < ?", max_time).order("#{table_name}.#{order} DESC")
-
end
-
-
1
def owned_by_user(user)
-
21
user.person.public_send(table_name)
-
end
-
-
1
private
-
-
1
def visible_by_user(user)
-
107
ShareVisibility.arel_table[:user_id].eq(user.id)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Diaspora
-
1
module Signature
-
1
def self.included(model)
-
3
model.class_eval do
-
3
belongs_to :signature_order
-
3
validates :signature_order, presence: true
-
-
3
validates :author_signature, presence: true
-
-
3
serialize :additional_data, Hash
-
-
3
def order
-
16
signature_order.order.split
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
1
module Diaspora
-
1
module Taggable
-
1
def self.included(model)
-
3
model.class_eval do
-
3
cattr_accessor :field_with_tags
-
-
# validate tag's name maximum length [tag's name should be less than or equal to 255 chars]
-
3
validate :tag_name_max_length, on: :create
-
-
# tag's name is limited to 255 charchters according to ActsAsTaggableOn gem, so we check the length of the name for each tag
-
3
def tag_name_max_length
-
10364
tag_list.each do |tag|
-
15074
errors.add(:tags, I18n.t("tags.name_too_long", count: 255, current_length: tag.length)) if tag.length > 255
-
end
-
end
-
3
protected :tag_name_max_length
-
end
-
3
model.instance_eval do
-
3
before_validation :build_tags # build tags before validation fixs the too long tag name issue #5737
-
-
3
def extract_tags_from sym
-
3
self.field_with_tags = sym
-
end
-
3
def field_with_tags_setter
-
129
"#{self.field_with_tags}=".to_sym
-
end
-
end
-
end
-
-
1
def build_tags
-
42275
self.tag_list = tag_strings
-
end
-
-
1
def tag_strings
-
42389
MessageRenderer::Processor.normalize(send(self.class.field_with_tags) || "")
-
.scan(/(?:^|\s)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|<3)/u)
-
.map(&:first)
-
.uniq(&:downcase)
-
end
-
-
1
def self.format_tags(text, opts={})
-
231
return text if opts[:plain_text]
-
-
228
text = ERB::Util.h(text) unless opts[:no_escape]
-
228
regex =/(^|\s|>)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|<3)/u
-
-
228
text.to_str.gsub(regex) { |matched_string|
-
192
pre, url_bit, clickable = $1, $2, "##{$2}"
-
192
if $2 == '<3'
-
# Special case for love, because the world needs more love.
-
10
url_bit = '<3'
-
end
-
-
192
%{#{pre}<a class="tag" href="/tags/#{url_bit}">#{clickable}</a>}
-
}.html_safe
-
end
-
-
1
def self.format_tags_for_mail(text)
-
203
regex = /(?<=^|\s|>)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|<3)/u
-
203
text.gsub(regex) do |tag|
-
17
opts = {name: ActsAsTaggableOn::Tag.normalize(tag)}
-
.merge(Rails.application.config.action_mailer.default_url_options)
-
17
"[#{tag}](#{Rails.application.routes.url_helpers.tag_url(opts)})"
-
end
-
end
-
end
-
end
-
# coding: utf-8
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class String
-
RTL_CLEANER_REGEXES = [ /@[^ ]+|#[^ ]+/u, # mention, tag
-
/^RT[: ]{1}| RT | RT: |[♺♻:]/u # retweet
-
]
-
-
def is_rtl?
-
return false if self.strip.empty?
-
detector = StringDirection::Detector.new(:dominant)
-
detector.rtl? self
-
end
-
-
# Diaspora specific
-
def cleaned_is_rtl?
-
string = String.new(self)
-
RTL_CLEANER_REGEXES.each do |cleaner|
-
string.gsub!(cleaner, '')
-
end
-
string.is_rtl?
-
end
-
end
-
# frozen_string_literal: true
-
-
class EmailInviter
-
attr_accessor :emails, :inviter, :locale
-
-
def initialize(emails, inviter, options={})
-
options = options.symbolize_keys
-
self.locale = options.fetch(:locale, 'en')
-
self.inviter = inviter
-
self.emails = emails
-
end
-
-
def emails=(list)
-
emails = list.split(%r{[,\s]+})
-
emails.reject!{|x| x == inviter.email } unless inviter.nil?
-
@emails = emails
-
end
-
-
def invitation_code
-
@invitation_code ||= inviter.invitation_code
-
end
-
-
def send!
-
self.emails.each{ |email| mail(email)}
-
end
-
-
private
-
-
def mail(email)
-
Notifier.invite(email, inviter, invitation_code, locale).deliver_now
-
end
-
end
-
-
# frozen_string_literal: true
-
-
# Inspired by https://github.com/route/errgent/blob/master/lib/errgent/renderer.rb
-
class ErrorPageRenderer
-
def initialize options={}
-
@codes = options.fetch :codes, [404, 500]
-
@output = options.fetch :output, "public/%s.html"
-
@template = options.fetch :template, "errors/error_%s"
-
@layout = options.fetch :layout, "layouts/error_page"
-
end
-
-
def render
-
@codes.each do |code|
-
path = Rails.root.join(@output % code)
-
File.write path, ApplicationController.render(@template % code, layout: @layout, locals: {code: code})
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module EvilQuery
-
class Base
-
include Diaspora::Logging
-
-
def fetch_ids!(relation, id_column)
-
#the relation should be ordered and limited by here
-
@class.connection.select_values(id_sql(relation, id_column))
-
end
-
-
def id_sql(relation, id_column)
-
@class.connection.unprepared_statement { relation.select(id_column).to_sql }
-
end
-
end
-
-
class Participation < Base
-
def initialize(user)
-
@user = user
-
@class = Post
-
end
-
-
def posts
-
author_id = @user.person_id
-
Post.joins("LEFT OUTER JOIN participations ON participations.target_id = posts.id AND " \
-
"participations.target_type = 'Post'")
-
.where(::Participation.arel_table[:author_id].eq(author_id).or(Post.arel_table[:author_id].eq(author_id)))
-
.order("posts.interacted_at DESC")
-
.distinct
-
end
-
end
-
-
class LikedPosts < Base
-
def initialize(user)
-
@user = user
-
end
-
-
def posts
-
Post.liked_by(@user.person)
-
end
-
end
-
-
class CommentedPosts < Base
-
def initialize(user)
-
@user = user
-
end
-
-
def posts
-
Post.commented_by(@user.person)
-
end
-
end
-
-
class MultiStream < Base
-
def initialize(user, order, max_time, include_spotlight)
-
@user = user
-
@class = Post
-
@order = order
-
@max_time = max_time
-
@include_spotlight = include_spotlight
-
end
-
-
def make_relation!
-
logger.debug("[EVIL-QUERY] make_relation!")
-
post_ids = aspects_post_ids! + ids!(followed_tags_posts!) + ids!(mentioned_posts)
-
post_ids += ids!(community_spotlight_posts!) if @include_spotlight
-
Post.where(:id => post_ids)
-
end
-
-
def aspects_post_ids!
-
logger.debug("[EVIL-QUERY] aspect_post_ids!")
-
@user.visible_shareable_ids(Post, limit: 15, order: "#{@order} DESC", max_time: @max_time, all_aspects?: true)
-
end
-
-
def followed_tags_posts!
-
logger.debug("[EVIL-QUERY] followed_tags_posts!")
-
StatusMessage.public_tag_stream(@user.followed_tag_ids).excluding_hidden_content(@user)
-
end
-
-
def mentioned_posts
-
logger.debug("[EVIL-QUERY] mentioned_posts")
-
StatusMessage.where_person_is_mentioned(@user.person)
-
end
-
-
def community_spotlight_posts!
-
Post.all_public.where(:author_id => fetch_ids!(Person.community_spotlight, 'people.id'))
-
end
-
-
def ids!(query)
-
fetch_ids!(query.for_a_stream(@max_time, @order), 'posts.id')
-
end
-
end
-
-
class VisibleShareableById < Base
-
def initialize(user, klass, key, id, conditions={})
-
@querent = user
-
@class = klass
-
@key = key
-
@id = id
-
@conditions = conditions
-
end
-
-
def post!
-
#small optimization - is this optimal order??
-
querent_has_visibility.first || querent_is_author.first || public_post.first
-
end
-
-
protected
-
-
def querent_has_visibility
-
@class.where(@key => @id).joins(:share_visibilities)
-
.where(share_visibilities: {user_id: @querent.id})
-
.where(@conditions)
-
.select(@class.table_name + ".*")
-
end
-
-
def querent_is_author
-
@class.where(@key => @id, :author_id => @querent.person.id).where(@conditions)
-
end
-
-
def public_post
-
@class.where(@key => @id, :public => true).where(@conditions)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module I18n
-
module Backend
-
module InterpolationFallbacks
-
def translate(locale, key, options = {})
-
default = extract_non_symbol_default!(options) if options[:default]
-
options.merge!(:default => default) if default
-
-
original_exception = nil
-
-
I18n.fallbacks[locale].each do |fallback|
-
begin
-
result = super(fallback, key, options)
-
return result unless result.nil?
-
rescue I18n::MissingInterpolationArgument, I18n::InvalidPluralizationData => e
-
original_exception ||= e
-
end
-
end
-
-
return super(locale, nil, options) if default
-
raise original_exception
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "pathname"
-
1
require "json-schema"
-
-
1
module NodeInfo
-
1
VERSIONS = %w[1.0 2.0 2.1].freeze
-
1
SCHEMAS = {} # rubocop:disable Style/MutableConstant
-
1
private_constant :SCHEMAS
-
-
1
Document = Struct.new(:version, :software, :protocols, :services, :open_registrations, :usage, :metadata) do
-
# rubocop:disable Lint/ConstantDefinitionInBlock
-
1
Software = Struct.new(:name, :version, :repository, :homepage) do
-
1
def initialize(name=nil, version=nil)
-
19
super(name, version)
-
end
-
-
1
def version_10_hash
-
{
-
39
"name" => name,
-
"version" => version
-
}
-
end
-
-
1
def version_21_hash
-
8
version_10_hash.merge(
-
"repository" => repository,
-
"homepage" => homepage
-
)
-
end
-
end
-
-
1
Protocols = Struct.new(:protocols) do
-
1
def initialize(protocols=[])
-
19
super(protocols)
-
end
-
-
1
def version_10_hash
-
{
-
21
"inbound" => protocols,
-
"outbound" => protocols
-
}
-
end
-
-
1
def version_20_array
-
18
protocols
-
end
-
end
-
-
1
Services = Struct.new(:inbound, :outbound) do
-
1
def initialize(inbound=[], outbound=[])
-
19
super(inbound, outbound)
-
end
-
-
1
def version_10_hash
-
{
-
39
"inbound" => inbound,
-
"outbound" => outbound
-
}
-
end
-
end
-
-
1
Usage = Struct.new(:users, :local_posts, :local_comments) do
-
1
Users = Struct.new(:total, :active_halfyear, :active_month) do
-
1
def initialize(total=nil, active_halfyear=nil, active_month=nil)
-
19
super(total, active_halfyear, active_month)
-
end
-
-
1
def version_10_hash
-
{
-
39
"total" => total,
-
"activeHalfyear" => active_halfyear,
-
"activeMonth" => active_month
-
}
-
end
-
end
-
-
1
def initialize(local_posts=nil, local_comments=nil)
-
19
super(Users.new, local_posts, local_comments)
-
end
-
-
1
def version_10_hash
-
{
-
39
"users" => users.version_10_hash,
-
"localPosts" => local_posts,
-
"localComments" => local_comments
-
}
-
end
-
end
-
# rubocop:enable Lint/ConstantDefinitionInBlock
-
-
1
def self.build
-
19
new.tap do |doc|
-
19
yield doc
-
19
doc.validate
-
end
-
end
-
-
1
def initialize(version=nil, open_registrations=nil, metadata={})
-
19
super(version, Software.new, Protocols.new, Services.new, open_registrations, Usage.new, metadata)
-
end
-
-
1
def as_json(_options={})
-
39
case version
-
when "1.0"
-
21
version_10_hash
-
when "2.0"
-
10
version_20_hash
-
when "2.1"
-
8
version_21_hash
-
end
-
end
-
-
1
def content_type
-
6
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/#{version}#"
-
end
-
-
1
def schema
-
19
NodeInfo.schema version
-
end
-
-
1
def validate
-
19
assert NodeInfo.supported_version?(version), "Unknown version #{version}"
-
19
JSON::Validator.validate!(schema, as_json)
-
end
-
-
1
private
-
-
1
def assert(condition, message)
-
19
raise ArgumentError, message unless condition
-
end
-
-
1
def version_10_hash
-
21
deep_compact(
-
"version" => "1.0",
-
"software" => software.version_10_hash,
-
"protocols" => protocols.version_10_hash,
-
"services" => services.version_10_hash,
-
"openRegistrations" => open_registrations,
-
"usage" => usage.version_10_hash,
-
"metadata" => metadata
-
)
-
end
-
-
1
def version_20_hash
-
10
deep_compact(
-
"version" => "2.0",
-
"software" => software.version_10_hash,
-
"protocols" => protocols.version_20_array,
-
"services" => services.version_10_hash,
-
"openRegistrations" => open_registrations,
-
"usage" => usage.version_10_hash,
-
"metadata" => metadata
-
)
-
end
-
-
1
def version_21_hash
-
8
deep_compact(
-
"version" => "2.1",
-
"software" => software.version_21_hash,
-
"protocols" => protocols.version_20_array,
-
"services" => services.version_10_hash,
-
"openRegistrations" => open_registrations,
-
"usage" => usage.version_10_hash,
-
"metadata" => metadata
-
)
-
end
-
-
1
def deep_compact(hash)
-
286
hash.tap do |hash|
-
286
hash.reject! {|_, value|
-
892
deep_compact value if value.is_a? Hash
-
892
value.nil?
-
}
-
end
-
end
-
end
-
-
1
def self.schema(version)
-
24
SCHEMAS[version] ||= JSON.parse(
-
Pathname.new(__dir__).join("..", "vendor", "nodeinfo", "schemas", "#{version}.json").expand_path.read
-
)
-
end
-
-
1
def self.build(&block)
-
19
Document.build(&block)
-
end
-
-
1
def self.jrd(endpoint)
-
{
-
2
"links" => VERSIONS.map {|version|
-
{
-
6
"rel" => "http://nodeinfo.diaspora.software/ns/schema/#{version}",
-
"href" => endpoint % {version: version}
-
}
-
}
-
}
-
end
-
-
1
def self.supported_version?(version)
-
29
VERSIONS.include? version
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class PhotoExporter
-
1
attr_reader :user
-
-
1
def initialize(user)
-
10
@user = user
-
end
-
-
1
def perform
-
9
temp_zip = Tempfile.new([user.username, "_photos.zip"])
-
begin
-
9
Zip::OutputStream.open(temp_zip.path) do |zip_output_stream|
-
9
user.photos.each do |photo|
-
4
export_photo(zip_output_stream, photo)
-
end
-
end
-
ensure
-
9
temp_zip.close
-
end
-
-
9
update_exported_photos_at(temp_zip)
-
end
-
-
1
private
-
-
1
def export_photo(zip_output_stream, photo)
-
4
photo_file = photo.unprocessed_image.file
-
4
if photo_file
-
4
photo_data = photo_file.read
-
3
zip_output_stream.put_next_entry(photo.remote_photo_name)
-
3
zip_output_stream.print(photo_data)
-
else
-
user.logger.info "Export photos error: No file for #{photo.remote_photo_name} not found"
-
end
-
rescue Errno::ENOENT
-
1
user.logger.info "Export photos error: #{photo.unprocessed_image.file.path} not found"
-
end
-
-
1
def update_exported_photos_at(temp_zip)
-
9
user.update exported_photos_file: temp_zip, exported_photos_at: Time.zone.now
-
ensure
-
9
user.restore_attributes if user.invalid?
-
9
user.update exporting_photos: false
-
end
-
end
-
# frozen_string_literal: true
-
-
class Publisher
-
attr_accessor :user, :open, :prefill, :public
-
-
def initialize(user, opts={})
-
self.user = user
-
self.open = opts[:open]
-
self.prefill = opts[:prefill]
-
self.public = opts[:public]
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
-
class Pubsubhubbub
-
def initialize(hub, options={})
-
@hub = hub
-
end
-
-
def publish(feed)
-
-
conn = Faraday.new do |c|
-
c.use Faraday::Request::UrlEncoded # encode request params as "www-form-urlencoded"
-
c.adapter Faraday::Adapter::NetHttp # perform requests with Net::HTTP
-
end
-
conn.post @hub, {'hub.url' => feed, 'hub.mode' => 'publish'}
-
end
-
end
-
# frozen_string_literal: true
-
-
#we dont have the environment, and it is not carring over from the migration
-
unless defined?(Person)
-
class Person < ApplicationRecord
-
belongs_to :owner, :class_name => 'User'
-
end
-
end
-
-
unless defined?(User)
-
class User < ApplicationRecord
-
serialize :hidden_shareables, Hash
-
end
-
end
-
-
unless defined?(Contact)
-
class Contact < ApplicationRecord
-
belongs_to :user
-
end
-
end
-
-
unless defined?(ShareVisibility)
-
class ShareVisibility < ApplicationRecord
-
belongs_to :contact
-
end
-
end
-
-
class ShareVisibilityConverter
-
RECENT = 2 # number of weeks to do in the migration
-
def self.copy_hidden_share_visibilities_to_users(only_recent = false)
-
query = ShareVisibility.where(:hidden => true).includes(:contact => :user)
-
query = query.where('share_visibilities.updated_at > ?', RECENT.weeks.ago) if only_recent
-
count = query.count
-
puts "Updating #{count} records in batches of 1000..."
-
-
batch_count = 1
-
query.find_in_batches do |visibilities|
-
puts "Updating batch ##{batch_count} of #{(count/1000)+1}..."
-
batch_count += 1
-
visibilities.each do |visibility|
-
begin
-
type = visibility.shareable_type
-
id = visibility.shareable_id.to_s
-
u = visibility.contact.user
-
u.hidden_shareables ||= {}
-
u.hidden_shareables[type] ||= []
-
u.hidden_shareables[type] << id unless u.hidden_shareables[type].include?(id)
-
u.save!(:validate => false)
-
rescue => e
-
puts "ERROR: #{e.message} skipping pv with id: #{visibility.id}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module SidekiqMiddlewares
-
class CleanAndShortBacktraces
-
def call(worker, item, queue)
-
yield
-
rescue Exception
-
backtrace = Rails.backtrace_cleaner.clean($!.backtrace)
-
backtrace.reject! { |line| line =~ /lib\/sidekiq_middlewares.rb/ }
-
limit = AppConfig.environment.sidekiq.backtrace.get
-
limit = limit ? limit.to_i : 0
-
backtrace = [] if limit == 0
-
raise $!, $!.message, backtrace[0..limit]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module Stream
-
require "stream/activity"
-
require "stream/aspect"
-
require "stream/comments"
-
require "stream/followed_tag"
-
require "stream/likes"
-
require "stream/mention"
-
require "stream/multi"
-
require "stream/person"
-
require "stream/public"
-
require "stream/local_public"
-
require "stream/tag"
-
end
-
# frozen_string_literal: true
-
-
class Stream::Activity < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.activity_streams_path(opts)
-
end
-
-
def order
-
"interacted_at"
-
end
-
-
def title
-
I18n.translate("streams.activity.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= EvilQuery::Participation.new(user).posts
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Aspect < Stream::Base
-
-
# @param user [User]
-
# @param inputted_aspect_ids [Array<Integer>] Ids of aspects for given stream
-
# @param aspect_ids [Array<Integer>] Aspects this stream is responsible for
-
# @opt max_time [Integer] Unix timestamp of stream's post ceiling
-
# @opt order [String] Order of posts (i.e. 'created_at', 'updated_at')
-
# @return [void]
-
def initialize(user, inputted_aspect_ids, opts={})
-
super(user, opts)
-
@inputted_aspect_ids = inputted_aspect_ids
-
end
-
-
# Filters aspects given the stream's aspect ids on initialization and the user.
-
# Will disclude aspects from inputted aspect ids if user is not associated with their
-
# target aspects.
-
#
-
# @return [ActiveRecord::Association<Aspect>] Filtered aspects given the stream's user
-
def aspects
-
@aspects ||= lambda do
-
a = user.aspects
-
a = a.where(:id => @inputted_aspect_ids) if @inputted_aspect_ids.any?
-
a
-
end.call
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
# NOTE(this should be something like Post.all_for_stream(@user, aspect_ids, {}) that calls visible_shareables
-
@posts ||= user.visible_shareables(Post, :all_aspects? => for_all_aspects?,
-
:by_members_of => aspect_ids,
-
:type => TYPES_OF_POST_IN_STREAM,
-
:order => "#{order} DESC",
-
:max_time => max_time
-
)
-
end
-
-
# @return [ActiveRecord::Association<Person>] AR association of people within stream's given aspects
-
def people
-
@people ||= Person.unique_from_aspects(aspect_ids, user).includes(:profile)
-
end
-
-
# @return [String] URL
-
def link(opts={})
-
Rails.application.routes.url_helpers.aspects_path(opts)
-
end
-
-
# The first aspect in #aspects, given the stream is not for all aspects, or #aspects size is 1
-
# @note aspects.first is used for mobile. NOTE(this is a hack and should be fixed)
-
# @return [Aspect,Symbol]
-
def aspect
-
if !for_all_aspects? || aspects.size == 1
-
aspects.first
-
end
-
end
-
-
# The title that will display at the top of the stream's
-
# publisher box.
-
#
-
# @return [String]
-
def title
-
if self.for_all_aspects?
-
I18n.t('streams.aspects.title')
-
else
-
self.aspects.to_sentence
-
end
-
end
-
-
# Determine whether or not the stream is displaying across
-
# all of the user's aspects.
-
#
-
# @return [Boolean]
-
def for_all_aspects?
-
@all_aspects ||= aspects.size == user.aspects.size
-
end
-
-
private
-
-
def aspect_ids
-
@aspect_ids ||= aspects.map(&:id)
-
end
-
end
-
# frozen_string_literal: true
-
-
class Stream::Base
-
TYPES_OF_POST_IN_STREAM = ['StatusMessage', 'Reshare']
-
-
attr_accessor :max_time, :order, :user, :publisher
-
-
def initialize(user, opts={})
-
self.user = user
-
self.max_time = opts[:max_time]
-
self.order = opts[:order]
-
self.publisher = Publisher.new(self.user, publisher_opts)
-
end
-
-
#requied to implement said stream
-
def link(opts={})
-
'change me in lib/base_stream.rb!'
-
end
-
-
def post_from_group(post)
-
[]
-
end
-
-
# @return [String]
-
def title
-
'a title'
-
end
-
-
# @return [ActiveRecord::Relation<Post>]
-
def posts
-
Post.all
-
end
-
-
# @return [Array<Post>]
-
def stream_posts
-
self.posts.for_a_stream(max_time, order, self.user).tap do |posts|
-
like_posts_for_stream!(posts) #some sql person could probably do this with joins.
-
end
-
end
-
-
# @return [ActiveRecord::Association<Person>] AR association of people within stream's given aspects
-
def people
-
people_ids = self.stream_posts.map{|x| x.author_id}
-
Person.where(:id => people_ids).
-
includes(:profile)
-
end
-
-
# @return [Boolean]
-
def for_all_aspects?
-
true
-
end
-
-
#NOTE: MBS bad bad methods the fact we need these means our views are foobared. please kill them and make them
-
#private methods on the streams that need them
-
def aspects
-
user.post_default_aspects
-
end
-
-
# @return [Aspect] The first aspect in #aspects
-
def aspect
-
aspects.first
-
end
-
-
def max_time=(time_string)
-
@max_time = Time.at(time_string.to_i) unless time_string.blank?
-
@max_time ||= (Time.now + 1)
-
end
-
-
def order=(order_string)
-
@order = order_string
-
@order ||= 'created_at'
-
end
-
-
protected
-
# @return [void]
-
def like_posts_for_stream!(posts)
-
return posts unless @user
-
-
likes = Like.where(:author_id => @user.person_id, :target_id => posts.map(&:id), :target_type => "Post")
-
-
like_hash = likes.inject({}) do |hash, like|
-
hash[like.target_id] = like
-
hash
-
end
-
-
posts.each do |post|
-
post.user_like = like_hash[post.id]
-
end
-
end
-
-
# @return [Hash]
-
def publisher_opts
-
{}
-
end
-
-
# Memoizes all Contacts present in the Stream
-
#
-
# @return [Array<Contact>]
-
def contacts_in_stream
-
@contacts_in_stream ||= Contact.where(:user_id => user.id, :person_id => people.map(&:id)).load
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Comments < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.comment_stream_path(opts)
-
end
-
-
def title
-
I18n.translate("streams.comment_stream.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= EvilQuery::CommentedPosts.new(user).posts
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::FollowedTag < Stream::Base
-
-
def link(opts={})
-
Rails.application.routes.url_helpers.tag_followings_path(opts)
-
end
-
-
def title
-
I18n.t('streams.followed_tag.title')
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= StatusMessage.user_tag_stream(user, tag_ids)
-
end
-
-
private
-
-
def tag_string
-
@tag_string ||= tags.join(', '){|tag| tag.name}.to_s
-
end
-
-
def tag_ids
-
tags.map{|x| x.id}
-
end
-
-
def tags
-
@tags = user.followed_tags
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Likes < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.like_stream_path(opts)
-
end
-
-
def title
-
I18n.translate("streams.like_stream.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= EvilQuery::LikedPosts.new(user).posts
-
end
-
end
-
# frozen_string_literal: true
-
-
# rubocop:disable Style/ClassAndModuleChildren
-
class Stream::LocalPublic < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.local_public_stream_path(opts)
-
end
-
-
def title
-
I18n.t("streams.local_public.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= Post.all_local_public
-
end
-
-
# Override base class method
-
def aspects
-
["public"]
-
end
-
end
-
# rubocop:enable Style/ClassAndModuleChildren
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Mention < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.mentions_path(opts)
-
end
-
-
def title
-
I18n.translate("streams.mentions.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= StatusMessage.where_person_is_mentioned(self.user.person)
-
end
-
end
-
# frozen_string_literal: true
-
-
class Stream::Multi < Stream::Base
-
-
# @return [String] URL
-
def link(opts)
-
Rails.application.routes.url_helpers.stream_path(opts)
-
end
-
-
# @return [String]
-
def title
-
I18n.t('streams.multi.title')
-
end
-
-
def posts
-
@posts ||= ::EvilQuery::MultiStream.new(user, order, max_time, include_community_spotlight?).make_relation!
-
end
-
-
#emits an enum of the groups which the post appeared
-
# :spotlight, :aspects, :tags, :mentioned
-
def post_from_group(post)
-
streams_included.collect do |source|
-
is_in?(source, post)
-
end.compact
-
end
-
-
private
-
-
def publisher_opts
-
if welcome?
-
{open: true, prefill: publisher_prefill, public: true}
-
else
-
{public: user.post_default_public}
-
end
-
end
-
-
# Generates the prefill for the publisher
-
#
-
# @return [String]
-
def publisher_prefill
-
prefill = I18n.t("shared.publisher.new_user_prefill.hello", :new_user_tag => I18n.t('shared.publisher.new_user_prefill.newhere'))
-
if self.user.followed_tags.size > 0
-
tag_string = self.user.followed_tags.map{|t| "##{t.name}"}.to_sentence
-
prefill << I18n.t("shared.publisher.new_user_prefill.i_like", :tags => tag_string)
-
end
-
-
if inviter = self.user.invited_by.try(:person)
-
prefill << I18n.t("shared.publisher.new_user_prefill.invited_by")
-
prefill << "@{#{inviter.diaspora_handle}}!"
-
end
-
-
prefill
-
end
-
-
# @return [Boolean]
-
def welcome?
-
self.user.getting_started
-
end
-
-
# @return [Array<Symbol>]
-
def streams_included
-
@streams_included ||= lambda do
-
array = [:mentioned, :aspects, :followed_tags]
-
array << :community_spotlight if include_community_spotlight?
-
array
-
end.call
-
end
-
-
# @return [Symbol]
-
def is_in?(sym, post)
-
if self.send("#{sym.to_s}_post_ids").find{|x| (x == post.id) || (x.to_s == post.id.to_s)}
-
"#{sym.to_s}_stream".to_sym
-
end
-
end
-
-
# @return [Boolean]
-
def include_community_spotlight?
-
AppConfig.settings.community_spotlight.enable? && user.show_community_spotlight_in_stream?
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Person < Stream::Base
-
-
attr_accessor :person
-
-
def initialize(user, person, opts={})
-
self.person = person
-
super(user, opts)
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= user.present? ? user.posts_from(@person) : @person.posts.where(:public => true)
-
end
-
-
# @return [Array<Post>]
-
def stream_posts
-
posts.for_a_stream(max_time, order, user, true).tap do |posts|
-
like_posts_for_stream!(posts) # some sql person could probably do this with joins.
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Public < Stream::Base
-
def link(opts={})
-
Rails.application.routes.url_helpers.public_stream_path(opts)
-
end
-
-
def title
-
I18n.translate("streams.public.title")
-
end
-
-
# @return [ActiveRecord::Association<Post>] AR association of posts
-
def posts
-
@posts ||= Post.all_public
-
end
-
-
# Override base class method
-
def aspects
-
["public"]
-
end
-
end
-
# frozen_string_literal: true
-
-
# Copyright (c) 2010-2011, Diaspora Inc. This file is
-
# licensed under the Affero General Public License version 3 or later. See
-
# the COPYRIGHT file.
-
-
class Stream::Tag < Stream::Base
-
attr_accessor :tag_name, :people_page , :people_per_page, :public_only
-
-
def initialize(user, tag_name, opts={})
-
self.tag_name = tag_name
-
self.people_page = opts[:page] || 1
-
self.people_per_page = 15
-
self.public_only = opts[:public_only] || false
-
super(user, opts)
-
end
-
-
def tag
-
@tag ||= ActsAsTaggableOn::Tag.named(tag_name).first
-
end
-
-
def display_tag_name
-
@display_tag_name ||= "##{tag_name}"
-
end
-
-
def tagged_people
-
@people ||= ::Person.profile_tagged_with(tag_name).paginate(:page => people_page, :per_page => people_per_page)
-
end
-
-
def tagged_people_count
-
@people_count ||= ::Person.profile_tagged_with(tag_name).count
-
end
-
-
def posts
-
return @posts unless @posts.nil?
-
-
if public_only || user.blank?
-
return @posts = StatusMessage.public_tag_stream(tag.id)
-
end
-
@posts = StatusMessage.user_tag_stream(user, tag.id)
-
end
-
-
def stream_posts
-
return [] unless tag
-
super
-
end
-
-
def tag_name=(tag_name)
-
@tag_name = tag_name.downcase.gsub('#', '')
-
end
-
-
private
-
-
# @return [Hash]
-
def publisher_opts
-
{:open => true}
-
end
-
end